Merge "ANDROID: binder: prevent double flushing when free buffer" into sc-dev
diff --git a/include/input/LatencyStatistics.h b/include/input/LatencyStatistics.h
deleted file mode 100644
index bd86266..0000000
--- a/include/input/LatencyStatistics.h
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2019 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.
- */
-
-#ifndef _UI_INPUT_STATISTICS_H
-#define _UI_INPUT_STATISTICS_H
-
-#include <android-base/chrono_utils.h>
-
-#include <stddef.h>
-
-namespace android {
-
-class LatencyStatistics {
-private:
- /* Minimum sample recorded */
- float mMin;
- /* Maximum sample recorded */
- float mMax;
- /* Sum of all samples recorded */
- float mSum;
- /* Sum of all the squares of samples recorded */
- float mSum2;
- /* Count of all samples recorded */
- size_t mCount;
- /* The last time statistics were reported */
- std::chrono::steady_clock::time_point mLastReportTime;
- /* Statistics Report Frequency */
- const std::chrono::seconds mReportPeriod;
-
-public:
- LatencyStatistics(std::chrono::seconds period);
-
- void addValue(float);
- void reset();
- bool shouldReport();
-
- float getMean();
- float getMin();
- float getMax();
- float getStDev();
- size_t getCount();
-};
-
-} // namespace android
-
-#endif // _UI_INPUT_STATISTICS_H
diff --git a/libs/input/Android.bp b/libs/input/Android.bp
index 6f79f4b..a63ec8f 100644
--- a/libs/input/Android.bp
+++ b/libs/input/Android.bp
@@ -49,7 +49,6 @@
"Keyboard.cpp",
"KeyCharacterMap.cpp",
"KeyLayoutMap.cpp",
- "LatencyStatistics.cpp",
"PropertyMap.cpp",
"TouchVideoFrame.cpp",
"VelocityControl.cpp",
diff --git a/libs/input/LatencyStatistics.cpp b/libs/input/LatencyStatistics.cpp
deleted file mode 100644
index 394da22..0000000
--- a/libs/input/LatencyStatistics.cpp
+++ /dev/null
@@ -1,90 +0,0 @@
-/*
- * Copyright (C) 2019 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.
- */
-
-#include <input/LatencyStatistics.h>
-
-#include <android-base/chrono_utils.h>
-
-#include <cmath>
-#include <limits>
-
-namespace android {
-
-LatencyStatistics::LatencyStatistics(std::chrono::seconds period) : mReportPeriod(period) {
- reset();
-}
-
-/**
- * Add a raw value to the statistics
- */
-void LatencyStatistics::addValue(float value) {
- if (value < mMin) {
- mMin = value;
- }
- if (value > mMax) {
- mMax = value;
- }
- mSum += value;
- mSum2 += value * value;
- mCount++;
-}
-
-/**
- * Get the mean. Should not be called if no samples have been added.
- */
-float LatencyStatistics::getMean() {
- return mSum / mCount;
-}
-
-/**
- * Get the standard deviation. Should not be called if no samples have been added.
- */
-float LatencyStatistics::getStDev() {
- float mean = getMean();
- return sqrt(mSum2 / mCount - mean * mean);
-}
-
-float LatencyStatistics::getMin() {
- return mMin;
-}
-
-float LatencyStatistics::getMax() {
- return mMax;
-}
-
-size_t LatencyStatistics::getCount() {
- return mCount;
-}
-
-/**
- * Reset internal state. The variable 'when' is the time when the data collection started.
- * Call this to start a new data collection window.
- */
-void LatencyStatistics::reset() {
- mMax = std::numeric_limits<float>::lowest();
- mMin = std::numeric_limits<float>::max();
- mSum = 0;
- mSum2 = 0;
- mCount = 0;
- mLastReportTime = std::chrono::steady_clock::now();
-}
-
-bool LatencyStatistics::shouldReport() {
- std::chrono::duration timeSinceReport = std::chrono::steady_clock::now() - mLastReportTime;
- return mCount != 0 && timeSinceReport >= mReportPeriod;
-}
-
-} // namespace android
diff --git a/libs/input/tests/Android.bp b/libs/input/tests/Android.bp
index 767878b..6ffc6a8 100644
--- a/libs/input/tests/Android.bp
+++ b/libs/input/tests/Android.bp
@@ -19,7 +19,6 @@
"InputEvent_test.cpp",
"InputPublisherAndConsumer_test.cpp",
"InputWindow_test.cpp",
- "LatencyStatistics_test.cpp",
"TouchVideoFrame_test.cpp",
"VelocityTracker_test.cpp",
"VerifiedInputEvent_test.cpp",
diff --git a/libs/input/tests/LatencyStatistics_test.cpp b/libs/input/tests/LatencyStatistics_test.cpp
deleted file mode 100644
index eb12d4e..0000000
--- a/libs/input/tests/LatencyStatistics_test.cpp
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2019 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.
- */
-
-#include <gtest/gtest.h>
-#include <input/LatencyStatistics.h>
-#include <cmath>
-#include <limits>
-#include <thread>
-
-namespace android {
-namespace test {
-
-TEST(LatencyStatisticsTest, ResetStats) {
- LatencyStatistics stats{5min};
- stats.addValue(5.0);
- stats.addValue(19.3);
- stats.addValue(20);
- stats.reset();
-
- ASSERT_EQ(stats.getCount(), 0u);
- ASSERT_EQ(std::isnan(stats.getStDev()), true);
- ASSERT_EQ(std::isnan(stats.getMean()), true);
-}
-
-TEST(LatencyStatisticsTest, AddStatsValue) {
- LatencyStatistics stats{5min};
- stats.addValue(5.0);
-
- ASSERT_EQ(stats.getMin(), 5.0);
- ASSERT_EQ(stats.getMax(), 5.0);
- ASSERT_EQ(stats.getCount(), 1u);
- ASSERT_EQ(stats.getMean(), 5.0);
- ASSERT_EQ(stats.getStDev(), 0.0);
-}
-
-TEST(LatencyStatisticsTest, AddMultipleStatsValue) {
- LatencyStatistics stats{5min};
- stats.addValue(4.0);
- stats.addValue(6.0);
- stats.addValue(8.0);
- stats.addValue(10.0);
-
- float stdev = stats.getStDev();
-
- ASSERT_EQ(stats.getMin(), 4.0);
- ASSERT_EQ(stats.getMax(), 10.0);
- ASSERT_EQ(stats.getCount(), 4u);
- ASSERT_EQ(stats.getMean(), 7.0);
- ASSERT_EQ(stdev * stdev, 5.0);
-}
-
-TEST(LatencyStatisticsTest, ShouldReportStats) {
- LatencyStatistics stats{0min};
- stats.addValue(5.0);
-
- std::this_thread::sleep_for(1us);
-
- ASSERT_EQ(stats.shouldReport(), true);
-}
-
-} // namespace test
-} // namespace android
\ No newline at end of file
diff --git a/services/inputflinger/Android.bp b/services/inputflinger/Android.bp
index 9b98a17..6612a93 100644
--- a/services/inputflinger/Android.bp
+++ b/services/inputflinger/Android.bp
@@ -63,11 +63,16 @@
"libcutils",
"libhidlbase",
"libinput",
+ "libkll",
"liblog",
+ "libprotobuf-cpp-lite",
"libstatslog",
+ "libstatspull",
+ "libstatssocket",
"libutils",
"libui",
"lib-platform-compat-native-api",
+ "server_configurable_flags",
],
static_libs: [
"libattestation",
diff --git a/services/inputflinger/benchmarks/Android.bp b/services/inputflinger/benchmarks/Android.bp
index ea37f4d..902bd0d 100644
--- a/services/inputflinger/benchmarks/Android.bp
+++ b/services/inputflinger/benchmarks/Android.bp
@@ -12,7 +12,10 @@
srcs: [
"InputDispatcher_benchmarks.cpp",
],
- defaults: ["inputflinger_defaults"],
+ defaults: [
+ "inputflinger_defaults",
+ "libinputdispatcher_defaults",
+ ],
shared_libs: [
"libbase",
"libbinder",
diff --git a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
index 1b5f1ab..bc77b8a 100644
--- a/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
+++ b/services/inputflinger/benchmarks/InputDispatcher_benchmarks.cpp
@@ -16,9 +16,11 @@
#include <benchmark/benchmark.h>
+#include <android/os/IInputConstants.h>
#include <binder/Binder.h>
#include "../dispatcher/InputDispatcher.h"
+using android::os::IInputConstants;
using android::os::InputEventInjectionResult;
using android::os::InputEventInjectionSync;
@@ -226,7 +228,7 @@
ui::Transform identityTransform;
MotionEvent event;
- event.initialize(InputEvent::nextId(), DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN,
+ event.initialize(IInputConstants::INVALID_INPUT_EVENT_ID, DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN,
ADISPLAY_ID_DEFAULT, INVALID_HMAC, AMOTION_EVENT_ACTION_DOWN,
/* actionButton */ 0, /* flags */ 0,
/* edgeFlags */ 0, AMETA_NONE, /* buttonState */ 0, MotionClassification::NONE,
@@ -252,9 +254,9 @@
const nsecs_t currentTime = now();
// Define a valid motion event.
- NotifyMotionArgs args(/* id */ 0, currentTime, currentTime, DEVICE_ID,
- AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT, POLICY_FLAG_PASS_TO_USER,
- AMOTION_EVENT_ACTION_DOWN,
+ NotifyMotionArgs args(IInputConstants::INVALID_INPUT_EVENT_ID, currentTime, currentTime,
+ DEVICE_ID, AINPUT_SOURCE_TOUCHSCREEN, ADISPLAY_ID_DEFAULT,
+ POLICY_FLAG_PASS_TO_USER, AMOTION_EVENT_ACTION_DOWN,
/* actionButton */ 0, /* flags */ 0, AMETA_NONE, /* buttonState */ 0,
MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, 1,
pointerProperties, pointerCoords,
@@ -283,14 +285,12 @@
for (auto _ : state) {
// Send ACTION_DOWN
motionArgs.action = AMOTION_EVENT_ACTION_DOWN;
- motionArgs.id = 0;
motionArgs.downTime = now();
motionArgs.eventTime = motionArgs.downTime;
dispatcher->notifyMotion(&motionArgs);
// Send ACTION_UP
motionArgs.action = AMOTION_EVENT_ACTION_UP;
- motionArgs.id = 1;
motionArgs.eventTime = now();
dispatcher->notifyMotion(&motionArgs);
diff --git a/services/inputflinger/dispatcher/Android.bp b/services/inputflinger/dispatcher/Android.bp
index 9750ef9..1b3888b 100644
--- a/services/inputflinger/dispatcher/Android.bp
+++ b/services/inputflinger/dispatcher/Android.bp
@@ -38,8 +38,11 @@
"InjectionState.cpp",
"InputDispatcher.cpp",
"InputDispatcherFactory.cpp",
+ "InputEventTimeline.cpp",
"InputState.cpp",
"InputTarget.cpp",
+ "LatencyAggregator.cpp",
+ "LatencyTracker.cpp",
"Monitor.cpp",
"TouchState.cpp",
"DragState.cpp",
@@ -54,11 +57,16 @@
"libcrypto",
"libcutils",
"libinput",
+ "libkll",
"liblog",
+ "libprotobuf-cpp-lite",
"libstatslog",
+ "libstatspull",
+ "libstatssocket",
"libui",
"libutils",
"lib-platform-compat-native-api",
+ "server_configurable_flags",
],
static_libs: [
"libattestation",
diff --git a/services/inputflinger/dispatcher/Entry.h b/services/inputflinger/dispatcher/Entry.h
index b5d3571..ebbd8e9 100644
--- a/services/inputflinger/dispatcher/Entry.h
+++ b/services/inputflinger/dispatcher/Entry.h
@@ -79,7 +79,7 @@
explicit ConfigurationChangedEntry(int32_t id, nsecs_t eventTime);
std::string getDescription() const override;
- virtual ~ConfigurationChangedEntry();
+ ~ConfigurationChangedEntry() override;
};
struct DeviceResetEntry : EventEntry {
@@ -88,7 +88,7 @@
DeviceResetEntry(int32_t id, nsecs_t eventTime, int32_t deviceId);
std::string getDescription() const override;
- virtual ~DeviceResetEntry();
+ ~DeviceResetEntry() override;
};
struct FocusEntry : EventEntry {
@@ -100,7 +100,7 @@
const std::string& reason);
std::string getDescription() const override;
- virtual ~FocusEntry();
+ ~FocusEntry() override;
};
struct PointerCaptureChangedEntry : EventEntry {
@@ -109,7 +109,7 @@
PointerCaptureChangedEntry(int32_t id, nsecs_t eventTime, bool hasPointerCapture);
std::string getDescription() const override;
- virtual ~PointerCaptureChangedEntry();
+ ~PointerCaptureChangedEntry() override;
};
struct DragEntry : EventEntry {
@@ -153,7 +153,7 @@
std::string getDescription() const override;
void recycle();
- virtual ~KeyEntry();
+ ~KeyEntry() override;
};
struct MotionEntry : EventEntry {
@@ -204,7 +204,7 @@
std::vector<float> values);
std::string getDescription() const override;
- virtual ~SensorEntry();
+ ~SensorEntry() override;
};
// Tracks the progress of dispatching a particular event to a particular connection.
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index 485a53a..790bd09 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -59,7 +59,6 @@
#include <log/log.h>
#include <log/log_event_list.h>
#include <powermanager/PowerManager.h>
-#include <statslog.h>
#include <unistd.h>
#include <utils/Trace.h>
@@ -447,6 +446,56 @@
return std::nullopt;
}
+static bool shouldReportMetricsForConnection(const Connection& connection) {
+ // Do not keep track of gesture monitors. They receive every event and would disproportionately
+ // affect the statistics.
+ if (connection.monitor) {
+ return false;
+ }
+ // If the connection is experiencing ANR, let's skip it. We have separate ANR metrics
+ if (!connection.responsive) {
+ return false;
+ }
+ return true;
+}
+
+static bool shouldReportFinishedEvent(const DispatchEntry& dispatchEntry,
+ const Connection& connection) {
+ const EventEntry& eventEntry = *dispatchEntry.eventEntry;
+ const int32_t& inputEventId = eventEntry.id;
+ if (inputEventId != dispatchEntry.resolvedEventId) {
+ // Event was transmuted
+ return false;
+ }
+ if (inputEventId == android::os::IInputConstants::INVALID_INPUT_EVENT_ID) {
+ return false;
+ }
+ // Only track latency for events that originated from hardware
+ if (eventEntry.isSynthesized()) {
+ return false;
+ }
+ const EventEntry::Type& inputEventEntryType = eventEntry.type;
+ if (inputEventEntryType == EventEntry::Type::KEY) {
+ const KeyEntry& keyEntry = static_cast<const KeyEntry&>(eventEntry);
+ if (keyEntry.flags & AKEY_EVENT_FLAG_CANCELED) {
+ return false;
+ }
+ } else if (inputEventEntryType == EventEntry::Type::MOTION) {
+ const MotionEntry& motionEntry = static_cast<const MotionEntry&>(eventEntry);
+ if (motionEntry.action == AMOTION_EVENT_ACTION_CANCEL ||
+ motionEntry.action == AMOTION_EVENT_ACTION_HOVER_EXIT) {
+ return false;
+ }
+ } else {
+ // Not a key or a motion
+ return false;
+ }
+ if (!shouldReportMetricsForConnection(connection)) {
+ return false;
+ }
+ return true;
+}
+
// --- InputDispatcher ---
InputDispatcher::InputDispatcher(const sp<InputDispatcherPolicyInterface>& policy)
@@ -468,6 +517,8 @@
mFocusedDisplayId(ADISPLAY_ID_DEFAULT),
mFocusedWindowRequestedPointerCapture(false),
mWindowTokenWithPointerCapture(nullptr),
+ mLatencyAggregator(),
+ mLatencyTracker(&mLatencyAggregator),
mCompatService(getCompatService()) {
mLooper = new Looper(false);
mReporter = createInputReporter();
@@ -2854,6 +2905,8 @@
"event",
connection->getInputChannelName().c_str());
#endif
+ // We keep the 'resolvedEventId' here equal to the original 'motionEntry.id' because
+ // this is a one-to-one event conversion.
dispatchEntry->resolvedAction = AMOTION_EVENT_ACTION_HOVER_ENTER;
}
@@ -2921,7 +2974,7 @@
// Enqueue the dispatch entry.
connection->outboundQueue.push_back(dispatchEntry.release());
- traceOutboundQueueLength(connection);
+ traceOutboundQueueLength(*connection);
}
/**
@@ -3102,7 +3155,6 @@
motionEntry.downTime, motionEntry.eventTime,
motionEntry.pointerCount,
motionEntry.pointerProperties, usingCoords);
- reportTouchEventForStatistics(motionEntry);
break;
}
@@ -3176,13 +3228,13 @@
connection->outboundQueue.erase(std::remove(connection->outboundQueue.begin(),
connection->outboundQueue.end(),
dispatchEntry));
- traceOutboundQueueLength(connection);
+ traceOutboundQueueLength(*connection);
connection->waitQueue.push_back(dispatchEntry);
if (connection->responsive) {
mAnrTracker.insert(dispatchEntry->timeoutTime,
connection->inputChannel->getConnectionToken());
}
- traceWaitQueueLength(connection);
+ traceWaitQueueLength(*connection);
}
}
@@ -3251,9 +3303,9 @@
// Clear the dispatch queues.
drainDispatchQueue(connection->outboundQueue);
- traceOutboundQueueLength(connection);
+ traceOutboundQueueLength(*connection);
drainDispatchQueue(connection->waitQueue);
- traceWaitQueueLength(connection);
+ traceWaitQueueLength(*connection);
// The connection appears to be unrecoverably broken.
// Ignore already broken or zombie connections.
@@ -3317,7 +3369,14 @@
finishDispatchCycleLocked(currentTime, connection, finish.seq, finish.handled,
finish.consumeTime);
} else if (std::holds_alternative<InputPublisher::Timeline>(*result)) {
- // TODO(b/167947340): Report this data to LatencyTracker
+ if (shouldReportMetricsForConnection(*connection)) {
+ const InputPublisher::Timeline& timeline =
+ std::get<InputPublisher::Timeline>(*result);
+ mLatencyTracker
+ .trackGraphicsLatency(timeline.inputEventId,
+ connection->inputChannel->getConnectionToken(),
+ std::move(timeline.graphicsTimeline));
+ }
}
gotOne = true;
}
@@ -3826,6 +3885,12 @@
args->xCursorPosition, args->yCursorPosition,
args->downTime, args->pointerCount,
args->pointerProperties, args->pointerCoords, 0, 0);
+ if (args->id != android::os::IInputConstants::INVALID_INPUT_EVENT_ID &&
+ IdGenerator::getSource(args->id) == IdGenerator::Source::INPUT_READER &&
+ !mInputFilterEnabled) {
+ const bool isDown = args->action == AMOTION_EVENT_ACTION_DOWN;
+ mLatencyTracker.trackListener(args->id, isDown, args->eventTime, args->readTime);
+ }
needWake = enqueueInboundEventLocked(std::move(newEntry));
mLock.unlock();
@@ -5049,6 +5114,8 @@
dump += StringPrintf(INDENT2 "KeyRepeatDelay: %" PRId64 "ms\n", ns2ms(mConfig.keyRepeatDelay));
dump += StringPrintf(INDENT2 "KeyRepeatTimeout: %" PRId64 "ms\n",
ns2ms(mConfig.keyRepeatTimeout));
+ dump += mLatencyTracker.dump(INDENT2);
+ dump += mLatencyAggregator.dump(INDENT2);
}
void InputDispatcher::dumpMonitors(std::string& dump, const std::vector<Monitor>& monitors) {
@@ -5141,6 +5208,8 @@
monitorsByDisplay[displayId].emplace_back(serverChannel, pid);
mLooper->addFd(fd, 0, ALOOPER_EVENT_INPUT, new LooperEventCallback(callback), nullptr);
+ ALOGI("Created monitor %s for display %" PRId32 ", gesture=%s, pid=%" PRId32, name.c_str(),
+ displayId, toString(isGestureMonitor), pid);
}
// Wake the looper because some connections have changed.
@@ -5622,7 +5691,12 @@
ALOGI("%s spent %" PRId64 "ms processing %s", connection->getWindowName().c_str(),
ns2ms(eventDuration), dispatchEntry->eventEntry->getDescription().c_str());
}
- reportDispatchStatistics(std::chrono::nanoseconds(eventDuration), *connection, handled);
+ if (shouldReportFinishedEvent(*dispatchEntry, *connection)) {
+ mLatencyTracker.trackFinishedEvent(dispatchEntry->eventEntry->id,
+ connection->inputChannel->getConnectionToken(),
+ dispatchEntry->deliveryTime, commandEntry->consumeTime,
+ finishTime);
+ }
bool restartEvent;
if (dispatchEntry->eventEntry->type == EventEntry::Type::KEY) {
@@ -5654,10 +5728,10 @@
processConnectionResponsiveLocked(*connection);
}
}
- traceWaitQueueLength(connection);
+ traceWaitQueueLength(*connection);
if (restartEvent && connection->status == Connection::STATUS_NORMAL) {
connection->outboundQueue.push_front(dispatchEntry);
- traceOutboundQueueLength(connection);
+ traceOutboundQueueLength(*connection);
} else {
releaseDispatchEntry(dispatchEntry);
}
@@ -5934,58 +6008,25 @@
mLock.lock();
}
-void InputDispatcher::reportDispatchStatistics(std::chrono::nanoseconds eventDuration,
- const Connection& connection, bool handled) {
- // TODO Write some statistics about how long we spend waiting.
-}
-
-/**
- * Report the touch event latency to the statsd server.
- * Input events are reported for statistics if:
- * - This is a touchscreen event
- * - InputFilter is not enabled
- * - Event is not injected or synthesized
- *
- * Statistics should be reported before calling addValue, to prevent a fresh new sample
- * from getting aggregated with the "old" data.
- */
-void InputDispatcher::reportTouchEventForStatistics(const MotionEntry& motionEntry)
- REQUIRES(mLock) {
- const bool reportForStatistics = (motionEntry.source == AINPUT_SOURCE_TOUCHSCREEN) &&
- !(motionEntry.isSynthesized()) && !mInputFilterEnabled;
- if (!reportForStatistics) {
- return;
- }
-
- if (mTouchStatistics.shouldReport()) {
- android::util::stats_write(android::util::TOUCH_EVENT_REPORTED, mTouchStatistics.getMin(),
- mTouchStatistics.getMax(), mTouchStatistics.getMean(),
- mTouchStatistics.getStDev(), mTouchStatistics.getCount());
- mTouchStatistics.reset();
- }
- const float latencyMicros = nanoseconds_to_microseconds(now() - motionEntry.eventTime);
- mTouchStatistics.addValue(latencyMicros);
-}
-
void InputDispatcher::traceInboundQueueLengthLocked() {
if (ATRACE_ENABLED()) {
ATRACE_INT("iq", mInboundQueue.size());
}
}
-void InputDispatcher::traceOutboundQueueLength(const sp<Connection>& connection) {
+void InputDispatcher::traceOutboundQueueLength(const Connection& connection) {
if (ATRACE_ENABLED()) {
char counterName[40];
- snprintf(counterName, sizeof(counterName), "oq:%s", connection->getWindowName().c_str());
- ATRACE_INT(counterName, connection->outboundQueue.size());
+ snprintf(counterName, sizeof(counterName), "oq:%s", connection.getWindowName().c_str());
+ ATRACE_INT(counterName, connection.outboundQueue.size());
}
}
-void InputDispatcher::traceWaitQueueLength(const sp<Connection>& connection) {
+void InputDispatcher::traceWaitQueueLength(const Connection& connection) {
if (ATRACE_ENABLED()) {
char counterName[40];
- snprintf(counterName, sizeof(counterName), "wq:%s", connection->getWindowName().c_str());
- ATRACE_INT(counterName, connection->waitQueue.size());
+ snprintf(counterName, sizeof(counterName), "wq:%s", connection.getWindowName().c_str());
+ ATRACE_INT(counterName, connection.waitQueue.size());
}
}
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index 6edc5f1..bb3f3e6 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -29,6 +29,8 @@
#include "InputState.h"
#include "InputTarget.h"
#include "InputThread.h"
+#include "LatencyAggregator.h"
+#include "LatencyTracker.h"
#include "Monitor.h"
#include "TouchState.h"
#include "TouchedWindow.h"
@@ -39,7 +41,6 @@
#include <input/InputApplication.h>
#include <input/InputTransport.h>
#include <input/InputWindow.h>
-#include <input/LatencyStatistics.h>
#include <limits.h>
#include <stddef.h>
#include <ui/Region.h>
@@ -636,15 +637,11 @@
void doOnPointerDownOutsideFocusLockedInterruptible(CommandEntry* commandEntry) REQUIRES(mLock);
// Statistics gathering.
- static constexpr std::chrono::duration TOUCH_STATS_REPORT_PERIOD = 5min;
- LatencyStatistics mTouchStatistics{TOUCH_STATS_REPORT_PERIOD};
-
- void reportTouchEventForStatistics(const MotionEntry& entry);
- void reportDispatchStatistics(std::chrono::nanoseconds eventDuration,
- const Connection& connection, bool handled);
+ LatencyAggregator mLatencyAggregator GUARDED_BY(mLock);
+ LatencyTracker mLatencyTracker GUARDED_BY(mLock);
void traceInboundQueueLengthLocked() REQUIRES(mLock);
- void traceOutboundQueueLength(const sp<Connection>& connection);
- void traceWaitQueueLength(const sp<Connection>& connection);
+ void traceOutboundQueueLength(const Connection& connection);
+ void traceWaitQueueLength(const Connection& connection);
sp<InputReporterInterface> mReporter;
sp<com::android::internal::compat::IPlatformCompatNative> mCompatService;
diff --git a/services/inputflinger/dispatcher/InputEventTimeline.cpp b/services/inputflinger/dispatcher/InputEventTimeline.cpp
new file mode 100644
index 0000000..3edb638
--- /dev/null
+++ b/services/inputflinger/dispatcher/InputEventTimeline.cpp
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+#include "InputEventTimeline.h"
+
+namespace android::inputdispatcher {
+
+ConnectionTimeline::ConnectionTimeline(nsecs_t deliveryTime, nsecs_t consumeTime,
+ nsecs_t finishTime)
+ : deliveryTime(deliveryTime),
+ consumeTime(consumeTime),
+ finishTime(finishTime),
+ mHasDispatchTimeline(true) {}
+
+ConnectionTimeline::ConnectionTimeline(std::array<nsecs_t, GraphicsTimeline::SIZE> graphicsTimeline)
+ : graphicsTimeline(std::move(graphicsTimeline)), mHasGraphicsTimeline(true) {}
+
+bool ConnectionTimeline::isComplete() const {
+ return mHasDispatchTimeline && mHasGraphicsTimeline;
+}
+
+bool ConnectionTimeline::setDispatchTimeline(nsecs_t inDeliveryTime, nsecs_t inConsumeTime,
+ nsecs_t inFinishTime) {
+ if (mHasDispatchTimeline) {
+ return false;
+ }
+ deliveryTime = inDeliveryTime;
+ consumeTime = inConsumeTime;
+ finishTime = inFinishTime;
+ mHasDispatchTimeline = true;
+ return true;
+}
+
+bool ConnectionTimeline::setGraphicsTimeline(std::array<nsecs_t, GraphicsTimeline::SIZE> timeline) {
+ if (mHasGraphicsTimeline) {
+ return false;
+ }
+ graphicsTimeline = std::move(timeline);
+ mHasGraphicsTimeline = true;
+ return true;
+}
+
+bool ConnectionTimeline::operator==(const ConnectionTimeline& rhs) const {
+ return deliveryTime == rhs.deliveryTime && consumeTime == rhs.consumeTime &&
+ finishTime == rhs.finishTime && graphicsTimeline == rhs.graphicsTimeline &&
+ mHasDispatchTimeline == rhs.mHasDispatchTimeline &&
+ mHasGraphicsTimeline == rhs.mHasGraphicsTimeline;
+}
+
+bool ConnectionTimeline::operator!=(const ConnectionTimeline& rhs) const {
+ return !operator==(rhs);
+}
+
+InputEventTimeline::InputEventTimeline(bool isDown, nsecs_t eventTime, nsecs_t readTime)
+ : isDown(isDown), eventTime(eventTime), readTime(readTime) {}
+
+bool InputEventTimeline::operator==(const InputEventTimeline& rhs) const {
+ if (connectionTimelines.size() != rhs.connectionTimelines.size()) {
+ return false;
+ }
+ for (const auto& [connectionToken, connectionTimeline] : connectionTimelines) {
+ auto it = rhs.connectionTimelines.find(connectionToken);
+ if (it == rhs.connectionTimelines.end()) {
+ return false;
+ }
+ if (connectionTimeline != it->second) {
+ return false;
+ }
+ }
+ return isDown == rhs.isDown && eventTime == rhs.eventTime && readTime == rhs.readTime;
+}
+
+} // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/InputEventTimeline.h b/services/inputflinger/dispatcher/InputEventTimeline.h
new file mode 100644
index 0000000..77b8472
--- /dev/null
+++ b/services/inputflinger/dispatcher/InputEventTimeline.h
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+#ifndef _UI_INPUT_INPUTDISPATCHER_INPUTEVENTTIMELINE_H
+#define _UI_INPUT_INPUTDISPATCHER_INPUTEVENTTIMELINE_H
+
+#include <binder/IBinder.h>
+#include <input/Input.h>
+#include <unordered_map>
+
+namespace android {
+
+namespace inputdispatcher {
+
+/**
+ * Describes the input event timeline for each connection.
+ * An event with the same inputEventId can go to more than 1 connection simultaneously.
+ * For each connection that the input event goes to, there will be a separate ConnectionTimeline
+ * created.
+ * To create a complete ConnectionTimeline, we must receive two calls:
+ * 1) setDispatchTimeline
+ * 2) setGraphicsTimeline
+ *
+ * In a typical scenario, the dispatch timeline is known first. Later, if a frame is produced, the
+ * graphics timeline is available.
+ */
+struct ConnectionTimeline {
+ // DispatchTimeline
+ nsecs_t deliveryTime; // time at which the event was sent to the receiver
+ nsecs_t consumeTime; // time at which the receiver read the event
+ nsecs_t finishTime; // time at which the finish event was received
+ // GraphicsTimeline
+ std::array<nsecs_t, GraphicsTimeline::SIZE> graphicsTimeline;
+
+ ConnectionTimeline(nsecs_t deliveryTime, nsecs_t consumeTime, nsecs_t finishTime);
+ ConnectionTimeline(std::array<nsecs_t, GraphicsTimeline::SIZE> graphicsTimeline);
+
+ /**
+ * True if all contained timestamps are valid, false otherwise.
+ */
+ bool isComplete() const;
+ /**
+ * Set the dispatching-related times. Return true if the operation succeeded, false if the
+ * dispatching times have already been set. If this function returns false, it likely indicates
+ * an error from the app side.
+ */
+ bool setDispatchTimeline(nsecs_t deliveryTime, nsecs_t consumeTime, nsecs_t finishTime);
+ /**
+ * Set the graphics-related times. Return true if the operation succeeded, false if the
+ * graphics times have already been set. If this function returns false, it likely indicates
+ * an error from the app side.
+ */
+ bool setGraphicsTimeline(std::array<nsecs_t, GraphicsTimeline::SIZE> graphicsTimeline);
+
+ inline bool operator==(const ConnectionTimeline& rhs) const;
+ inline bool operator!=(const ConnectionTimeline& rhs) const;
+
+private:
+ bool mHasDispatchTimeline = false;
+ bool mHasGraphicsTimeline = false;
+};
+
+struct InputEventTimeline {
+ InputEventTimeline(bool isDown, nsecs_t eventTime, nsecs_t readTime);
+ const bool isDown; // True if this is an ACTION_DOWN event
+ const nsecs_t eventTime;
+ const nsecs_t readTime;
+
+ struct IBinderHash {
+ std::size_t operator()(const sp<IBinder>& b) const {
+ return std::hash<IBinder*>{}(b.get());
+ }
+ };
+
+ std::unordered_map<sp<IBinder>, ConnectionTimeline, IBinderHash> connectionTimelines;
+
+ bool operator==(const InputEventTimeline& rhs) const;
+};
+
+class InputEventTimelineProcessor {
+protected:
+ InputEventTimelineProcessor() {}
+ virtual ~InputEventTimelineProcessor() {}
+
+public:
+ /**
+ * Process the provided timeline
+ */
+ virtual void processTimeline(const InputEventTimeline& timeline) = 0;
+};
+
+} // namespace inputdispatcher
+} // namespace android
+
+#endif // _UI_INPUT_INPUTDISPATCHER_INPUTEVENTTIMELINE_H
diff --git a/services/inputflinger/dispatcher/LatencyAggregator.cpp b/services/inputflinger/dispatcher/LatencyAggregator.cpp
new file mode 100644
index 0000000..a5bfc25
--- /dev/null
+++ b/services/inputflinger/dispatcher/LatencyAggregator.cpp
@@ -0,0 +1,282 @@
+/*
+ * Copyright (C) 2021 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 "LatencyAggregator"
+#include "LatencyAggregator.h"
+
+#include <inttypes.h>
+
+#include <android-base/stringprintf.h>
+#include <input/Input.h>
+#include <log/log.h>
+#include <server_configurable_flags/get_flags.h>
+
+using android::base::StringPrintf;
+using dist_proc::aggregation::KllQuantile;
+using std::chrono_literals::operator""ms;
+
+// Convert the provided nanoseconds into hundreds of microseconds.
+// Use hundreds of microseconds (as opposed to microseconds) to preserve space.
+static inline int64_t ns2hus(nsecs_t nanos) {
+ return ns2us(nanos) / 100;
+}
+
+// The maximum number of events that we will store in the statistics. Any events that we will
+// receive after we have reached this number will be ignored. We could also implement this by
+// checking the actual size of the current data and making sure that we do not go over. However,
+// the serialization process of sketches is too heavy (1 ms for all 14 sketches), and would be too
+// much to do (even if infrequently).
+// The value here has been determined empirically.
+static constexpr size_t MAX_EVENTS_FOR_STATISTICS = 20000;
+
+// Category (=namespace) name for the input settings that are applied at boot time
+static const char* INPUT_NATIVE_BOOT = "input_native_boot";
+// Feature flag name for the threshold of end-to-end touch latency that would trigger
+// SlowEventReported atom to be pushed
+static const char* SLOW_EVENT_MIN_REPORTING_LATENCY_MILLIS =
+ "slow_event_min_reporting_latency_millis";
+// Feature flag name for the minimum delay before reporting a slow event after having just reported
+// a slow event. This helps limit the amount of data sent to the server
+static const char* SLOW_EVENT_MIN_REPORTING_INTERVAL_MILLIS =
+ "slow_event_min_reporting_interval_millis";
+
+// If an event has end-to-end latency > 200 ms, it will get reported as a slow event.
+std::chrono::milliseconds DEFAULT_SLOW_EVENT_MIN_REPORTING_LATENCY = 200ms;
+// If we receive two slow events less than 1 min apart, we will only report 1 of them.
+std::chrono::milliseconds DEFAULT_SLOW_EVENT_MIN_REPORTING_INTERVAL = 60000ms;
+
+static std::chrono::milliseconds getSlowEventMinReportingLatency() {
+ std::string millis = server_configurable_flags::
+ GetServerConfigurableFlag(INPUT_NATIVE_BOOT, SLOW_EVENT_MIN_REPORTING_LATENCY_MILLIS,
+ std::to_string(
+ DEFAULT_SLOW_EVENT_MIN_REPORTING_LATENCY.count()));
+ return std::chrono::milliseconds(std::stoi(millis));
+}
+
+static std::chrono::milliseconds getSlowEventMinReportingInterval() {
+ std::string millis = server_configurable_flags::
+ GetServerConfigurableFlag(INPUT_NATIVE_BOOT, SLOW_EVENT_MIN_REPORTING_INTERVAL_MILLIS,
+ std::to_string(
+ DEFAULT_SLOW_EVENT_MIN_REPORTING_INTERVAL.count()));
+ return std::chrono::milliseconds(std::stoi(millis));
+}
+
+namespace android::inputdispatcher {
+
+/**
+ * Same as android::util::BytesField, but doesn't store raw pointers, and therefore deletes its
+ * resources automatically.
+ */
+class SafeBytesField {
+public:
+ explicit SafeBytesField(dist_proc::aggregation::KllQuantile& quantile) {
+ const zetasketch::android::AggregatorStateProto aggProto = quantile.SerializeToProto();
+ mBuffer.resize(aggProto.ByteSizeLong());
+ aggProto.SerializeToArray(mBuffer.data(), mBuffer.size());
+ }
+ android::util::BytesField getBytesField() {
+ return android::util::BytesField(mBuffer.data(), mBuffer.size());
+ }
+
+private:
+ std::vector<char> mBuffer;
+};
+
+LatencyAggregator::LatencyAggregator() {
+ AStatsManager_setPullAtomCallback(android::util::INPUT_EVENT_LATENCY_SKETCH, nullptr,
+ LatencyAggregator::pullAtomCallback, this);
+ dist_proc::aggregation::KllQuantileOptions options;
+ options.set_inv_eps(100); // Request precision of 1.0%, instead of default 0.1%
+ for (size_t i = 0; i < SketchIndex::SIZE; i++) {
+ mDownSketches[i] = KllQuantile::Create(options);
+ mMoveSketches[i] = KllQuantile::Create(options);
+ }
+}
+
+LatencyAggregator::~LatencyAggregator() {
+ AStatsManager_clearPullAtomCallback(android::util::INPUT_EVENT_LATENCY_SKETCH);
+}
+
+AStatsManager_PullAtomCallbackReturn LatencyAggregator::pullAtomCallback(int32_t atomTag,
+ AStatsEventList* data,
+ void* cookie) {
+ LatencyAggregator* pAggregator = reinterpret_cast<LatencyAggregator*>(cookie);
+ if (pAggregator == nullptr) {
+ LOG_ALWAYS_FATAL("pAggregator is null!");
+ }
+ return pAggregator->pullData(data);
+}
+
+void LatencyAggregator::processTimeline(const InputEventTimeline& timeline) {
+ processStatistics(timeline);
+ processSlowEvent(timeline);
+}
+
+void LatencyAggregator::processStatistics(const InputEventTimeline& timeline) {
+ // Before we do any processing, check that we have not yet exceeded MAX_SIZE
+ if (mNumSketchEventsProcessed >= MAX_EVENTS_FOR_STATISTICS) {
+ return;
+ }
+ mNumSketchEventsProcessed++;
+
+ std::array<std::unique_ptr<KllQuantile>, SketchIndex::SIZE>& sketches =
+ timeline.isDown ? mDownSketches : mMoveSketches;
+
+ // Process common ones first
+ const nsecs_t eventToRead = timeline.readTime - timeline.eventTime;
+ sketches[SketchIndex::EVENT_TO_READ]->Add(ns2hus(eventToRead));
+
+ // Now process per-connection ones
+ for (const auto& [connectionToken, connectionTimeline] : timeline.connectionTimelines) {
+ if (!connectionTimeline.isComplete()) {
+ continue;
+ }
+ const nsecs_t readToDeliver = connectionTimeline.deliveryTime - timeline.readTime;
+ const nsecs_t deliverToConsume =
+ connectionTimeline.consumeTime - connectionTimeline.deliveryTime;
+ const nsecs_t consumeToFinish =
+ connectionTimeline.finishTime - connectionTimeline.consumeTime;
+ const nsecs_t gpuCompletedTime =
+ connectionTimeline.graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME];
+ const nsecs_t presentTime =
+ connectionTimeline.graphicsTimeline[GraphicsTimeline::PRESENT_TIME];
+ const nsecs_t consumeToGpuComplete = gpuCompletedTime - connectionTimeline.consumeTime;
+ const nsecs_t gpuCompleteToPresent = presentTime - gpuCompletedTime;
+ const nsecs_t endToEnd = presentTime - timeline.eventTime;
+
+ sketches[SketchIndex::READ_TO_DELIVER]->Add(ns2hus(readToDeliver));
+ sketches[SketchIndex::DELIVER_TO_CONSUME]->Add(ns2hus(deliverToConsume));
+ sketches[SketchIndex::CONSUME_TO_FINISH]->Add(ns2hus(consumeToFinish));
+ sketches[SketchIndex::CONSUME_TO_GPU_COMPLETE]->Add(ns2hus(consumeToGpuComplete));
+ sketches[SketchIndex::GPU_COMPLETE_TO_PRESENT]->Add(ns2hus(gpuCompleteToPresent));
+ sketches[SketchIndex::END_TO_END]->Add(ns2hus(endToEnd));
+ }
+}
+
+AStatsManager_PullAtomCallbackReturn LatencyAggregator::pullData(AStatsEventList* data) {
+ std::array<std::unique_ptr<SafeBytesField>, SketchIndex::SIZE> serializedDownData;
+ std::array<std::unique_ptr<SafeBytesField>, SketchIndex::SIZE> serializedMoveData;
+ for (size_t i = 0; i < SketchIndex::SIZE; i++) {
+ serializedDownData[i] = std::make_unique<SafeBytesField>(*mDownSketches[i]);
+ serializedMoveData[i] = std::make_unique<SafeBytesField>(*mMoveSketches[i]);
+ }
+ android::util::
+ addAStatsEvent(data, android::util::INPUT_EVENT_LATENCY_SKETCH,
+ // DOWN sketches
+ serializedDownData[SketchIndex::EVENT_TO_READ]->getBytesField(),
+ serializedDownData[SketchIndex::READ_TO_DELIVER]->getBytesField(),
+ serializedDownData[SketchIndex::DELIVER_TO_CONSUME]->getBytesField(),
+ serializedDownData[SketchIndex::CONSUME_TO_FINISH]->getBytesField(),
+ serializedDownData[SketchIndex::CONSUME_TO_GPU_COMPLETE]
+ ->getBytesField(),
+ serializedDownData[SketchIndex::GPU_COMPLETE_TO_PRESENT]
+ ->getBytesField(),
+ serializedDownData[SketchIndex::END_TO_END]->getBytesField(),
+ // MOVE sketches
+ serializedMoveData[SketchIndex::EVENT_TO_READ]->getBytesField(),
+ serializedMoveData[SketchIndex::READ_TO_DELIVER]->getBytesField(),
+ serializedMoveData[SketchIndex::DELIVER_TO_CONSUME]->getBytesField(),
+ serializedMoveData[SketchIndex::CONSUME_TO_FINISH]->getBytesField(),
+ serializedMoveData[SketchIndex::CONSUME_TO_GPU_COMPLETE]
+ ->getBytesField(),
+ serializedMoveData[SketchIndex::GPU_COMPLETE_TO_PRESENT]
+ ->getBytesField(),
+ serializedMoveData[SketchIndex::END_TO_END]->getBytesField());
+
+ for (size_t i = 0; i < SketchIndex::SIZE; i++) {
+ mDownSketches[i]->Reset();
+ mMoveSketches[i]->Reset();
+ }
+ // Start new aggregations
+ mNumSketchEventsProcessed = 0;
+ return AStatsManager_PULL_SUCCESS;
+}
+
+void LatencyAggregator::processSlowEvent(const InputEventTimeline& timeline) {
+ static const std::chrono::duration sSlowEventThreshold = getSlowEventMinReportingLatency();
+ static const std::chrono::duration sSlowEventReportingInterval =
+ getSlowEventMinReportingInterval();
+ for (const auto& [token, connectionTimeline] : timeline.connectionTimelines) {
+ if (!connectionTimeline.isComplete()) {
+ continue;
+ }
+ mNumEventsSinceLastSlowEventReport++;
+ const nsecs_t presentTime =
+ connectionTimeline.graphicsTimeline[GraphicsTimeline::PRESENT_TIME];
+ const std::chrono::nanoseconds endToEndLatency =
+ std::chrono::nanoseconds(presentTime - timeline.eventTime);
+ if (endToEndLatency < sSlowEventThreshold) {
+ continue;
+ }
+ // This is a slow event. Before we report it, check if we are reporting too often
+ const std::chrono::duration elapsedSinceLastReport =
+ std::chrono::nanoseconds(timeline.eventTime - mLastSlowEventTime);
+ if (elapsedSinceLastReport < sSlowEventReportingInterval) {
+ mNumSkippedSlowEvents++;
+ continue;
+ }
+
+ const nsecs_t eventToRead = timeline.readTime - timeline.eventTime;
+ const nsecs_t readToDeliver = connectionTimeline.deliveryTime - timeline.readTime;
+ const nsecs_t deliverToConsume =
+ connectionTimeline.consumeTime - connectionTimeline.deliveryTime;
+ const nsecs_t consumeToFinish =
+ connectionTimeline.finishTime - connectionTimeline.consumeTime;
+ const nsecs_t gpuCompletedTime =
+ connectionTimeline.graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME];
+ const nsecs_t consumeToGpuComplete = gpuCompletedTime - connectionTimeline.consumeTime;
+ const nsecs_t gpuCompleteToPresent = presentTime - gpuCompletedTime;
+
+ android::util::stats_write(android::util::SLOW_INPUT_EVENT_REPORTED, timeline.isDown,
+ static_cast<int32_t>(ns2us(eventToRead)),
+ static_cast<int32_t>(ns2us(readToDeliver)),
+ static_cast<int32_t>(ns2us(deliverToConsume)),
+ static_cast<int32_t>(ns2us(consumeToFinish)),
+ static_cast<int32_t>(ns2us(consumeToGpuComplete)),
+ static_cast<int32_t>(ns2us(gpuCompleteToPresent)),
+ static_cast<int32_t>(ns2us(endToEndLatency.count())),
+ static_cast<int32_t>(mNumEventsSinceLastSlowEventReport),
+ static_cast<int32_t>(mNumSkippedSlowEvents));
+ mNumEventsSinceLastSlowEventReport = 0;
+ mNumSkippedSlowEvents = 0;
+ mLastSlowEventTime = timeline.readTime;
+ }
+}
+
+std::string LatencyAggregator::dump(const char* prefix) {
+ std::string sketchDump = StringPrintf("%s Sketches:\n", prefix);
+ for (size_t i = 0; i < SketchIndex::SIZE; i++) {
+ const int64_t numDown = mDownSketches[i]->num_values();
+ SafeBytesField downBytesField(*mDownSketches[i]);
+ const float downBytesKb = downBytesField.getBytesField().arg_length * 1E-3;
+ const int64_t numMove = mMoveSketches[i]->num_values();
+ SafeBytesField moveBytesField(*mMoveSketches[i]);
+ const float moveBytesKb = moveBytesField.getBytesField().arg_length * 1E-3;
+ sketchDump +=
+ StringPrintf("%s mDownSketches[%zu]->num_values = %" PRId64 " size = %.1fKB"
+ " mMoveSketches[%zu]->num_values = %" PRId64 " size = %.1fKB\n",
+ prefix, i, numDown, downBytesKb, i, numMove, moveBytesKb);
+ }
+
+ return StringPrintf("%sLatencyAggregator:\n", prefix) + sketchDump +
+ StringPrintf("%s mNumSketchEventsProcessed=%zu\n", prefix, mNumSketchEventsProcessed) +
+ StringPrintf("%s mLastSlowEventTime=%" PRId64 "\n", prefix, mLastSlowEventTime) +
+ StringPrintf("%s mNumEventsSinceLastSlowEventReport = %zu\n", prefix,
+ mNumEventsSinceLastSlowEventReport) +
+ StringPrintf("%s mNumSkippedSlowEvents = %zu\n", prefix, mNumSkippedSlowEvents);
+}
+
+} // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/LatencyAggregator.h b/services/inputflinger/dispatcher/LatencyAggregator.h
new file mode 100644
index 0000000..ed5731f
--- /dev/null
+++ b/services/inputflinger/dispatcher/LatencyAggregator.h
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+#ifndef _UI_INPUT_INPUTDISPATCHER_LATENCYAGGREGATOR_H
+#define _UI_INPUT_INPUTDISPATCHER_LATENCYAGGREGATOR_H
+
+#include <kll.h>
+#include <statslog.h>
+#include <utils/Timers.h>
+
+#include "InputEventTimeline.h"
+
+namespace android::inputdispatcher {
+
+enum SketchIndex : size_t {
+ EVENT_TO_READ = 0,
+ READ_TO_DELIVER = 1,
+ DELIVER_TO_CONSUME = 2,
+ CONSUME_TO_FINISH = 3,
+ CONSUME_TO_GPU_COMPLETE = 4,
+ GPU_COMPLETE_TO_PRESENT = 5,
+ END_TO_END = 6, // EVENT_TO_PRESENT
+ SIZE = 7, // Must be last
+};
+
+// Let's create a full timeline here:
+// eventTime
+// readTime
+// <---- after this point, the data becomes per-connection
+// deliveryTime // time at which the event was sent to the receiver
+// consumeTime // time at which the receiver read the event
+// finishTime // time at which the finish event was received
+// GraphicsTimeline::GPU_COMPLETED_TIME
+// GraphicsTimeline::PRESENT_TIME
+
+/**
+ * Keep sketches of the provided events and report slow events
+ */
+class LatencyAggregator final : public InputEventTimelineProcessor {
+public:
+ LatencyAggregator();
+ /**
+ * Record a complete event timeline
+ */
+ void processTimeline(const InputEventTimeline& timeline) override;
+
+ std::string dump(const char* prefix);
+
+ ~LatencyAggregator();
+
+private:
+ static AStatsManager_PullAtomCallbackReturn pullAtomCallback(int32_t atom_tag,
+ AStatsEventList* data,
+ void* cookie);
+ AStatsManager_PullAtomCallbackReturn pullData(AStatsEventList* data);
+ // ---------- Slow event handling ----------
+ void processSlowEvent(const InputEventTimeline& timeline);
+ nsecs_t mLastSlowEventTime = 0;
+ // How many slow events have been skipped due to rate limiting
+ size_t mNumSkippedSlowEvents = 0;
+ // How many events have been received since the last time we reported a slow event
+ size_t mNumEventsSinceLastSlowEventReport = 0;
+
+ // ---------- Statistics handling ----------
+ void processStatistics(const InputEventTimeline& timeline);
+ // Sketches
+ std::array<std::unique_ptr<dist_proc::aggregation::KllQuantile>, SketchIndex::SIZE>
+ mDownSketches;
+ std::array<std::unique_ptr<dist_proc::aggregation::KllQuantile>, SketchIndex::SIZE>
+ mMoveSketches;
+ // How many events have been processed so far
+ size_t mNumSketchEventsProcessed = 0;
+};
+
+} // namespace android::inputdispatcher
+
+#endif // _UI_INPUT_INPUTDISPATCHER_LATENCYAGGREGATOR_H
diff --git a/services/inputflinger/dispatcher/LatencyTracker.cpp b/services/inputflinger/dispatcher/LatencyTracker.cpp
new file mode 100644
index 0000000..d634dcd
--- /dev/null
+++ b/services/inputflinger/dispatcher/LatencyTracker.cpp
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2021 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 "LatencyTracker"
+#include "LatencyTracker.h"
+
+#include <inttypes.h>
+
+#include <android-base/properties.h>
+#include <android-base/stringprintf.h>
+#include <android/os/IInputConstants.h>
+#include <input/Input.h>
+#include <log/log.h>
+
+using android::base::HwTimeoutMultiplier;
+using android::base::StringPrintf;
+
+namespace android::inputdispatcher {
+
+/**
+ * Events that are older than this time will be considered mature, at which point we will stop
+ * waiting for the apps to provide further information about them.
+ * It's likely that the apps will ANR if the events are not received by this deadline, and we
+ * already track ANR metrics separately.
+ */
+const std::chrono::duration ANR_TIMEOUT = std::chrono::milliseconds(
+ android::os::IInputConstants::UNMULTIPLIED_DEFAULT_DISPATCHING_TIMEOUT_MILLIS *
+ HwTimeoutMultiplier());
+
+static bool isMatureEvent(nsecs_t eventTime, nsecs_t now) {
+ std::chrono::duration age = std::chrono::nanoseconds(now) - std::chrono::nanoseconds(eventTime);
+ return age > ANR_TIMEOUT;
+}
+
+/**
+ * A multimap allows to have several entries with the same key. This function just erases a specific
+ * key-value pair. Equivalent to the imaginary std api std::multimap::erase(key, value).
+ */
+template <typename K, typename V>
+static void eraseByKeyAndValue(std::multimap<K, V>& map, K key, V value) {
+ auto iterpair = map.equal_range(key);
+
+ for (auto it = iterpair.first; it != iterpair.second; ++it) {
+ if (it->second == value) {
+ map.erase(it);
+ break;
+ }
+ }
+}
+
+LatencyTracker::LatencyTracker(InputEventTimelineProcessor* processor)
+ : mTimelineProcessor(processor) {
+ LOG_ALWAYS_FATAL_IF(processor == nullptr);
+}
+
+void LatencyTracker::trackListener(int32_t inputEventId, bool isDown, nsecs_t eventTime,
+ nsecs_t readTime) {
+ reportAndPruneMatureRecords(eventTime);
+ const auto it = mTimelines.find(inputEventId);
+ if (it != mTimelines.end()) {
+ // Input event ids are randomly generated, so it's possible that two events have the same
+ // event id. Drop this event, and also drop the existing event because the apps would
+ // confuse us by reporting the rest of the timeline for one of them. This should happen
+ // rarely, so we won't lose much data
+ mTimelines.erase(it);
+ // In case we have another input event with a different id and at the same eventTime,
+ // only erase this specific inputEventId.
+ eraseByKeyAndValue(mEventTimes, eventTime, inputEventId);
+ return;
+ }
+ mTimelines.emplace(inputEventId, InputEventTimeline(isDown, eventTime, readTime));
+ mEventTimes.emplace(eventTime, inputEventId);
+}
+
+void LatencyTracker::trackFinishedEvent(int32_t inputEventId, const sp<IBinder>& connectionToken,
+ nsecs_t deliveryTime, nsecs_t consumeTime,
+ nsecs_t finishTime) {
+ const auto it = mTimelines.find(inputEventId);
+ if (it == mTimelines.end()) {
+ // It's possible that an app sends a bad (or late)'Finish' signal, since it's free to do
+ // anything in its process. Just drop the report and move on.
+ return;
+ }
+
+ InputEventTimeline& timeline = it->second;
+ const auto connectionIt = timeline.connectionTimelines.find(connectionToken);
+ if (connectionIt == timeline.connectionTimelines.end()) {
+ // Most likely case: app calls 'finishInputEvent' before it reports the graphics timeline
+ timeline.connectionTimelines.emplace(connectionToken,
+ ConnectionTimeline{deliveryTime, consumeTime,
+ finishTime});
+ } else {
+ // Already have a record for this connectionToken
+ ConnectionTimeline& connectionTimeline = connectionIt->second;
+ const bool success =
+ connectionTimeline.setDispatchTimeline(deliveryTime, consumeTime, finishTime);
+ if (!success) {
+ // We are receiving unreliable data from the app. Just delete the entire connection
+ // timeline for this event
+ timeline.connectionTimelines.erase(connectionIt);
+ }
+ }
+}
+
+void LatencyTracker::trackGraphicsLatency(
+ int32_t inputEventId, const sp<IBinder>& connectionToken,
+ std::array<nsecs_t, GraphicsTimeline::SIZE> graphicsTimeline) {
+ const auto it = mTimelines.find(inputEventId);
+ if (it == mTimelines.end()) {
+ // It's possible that an app sends a bad (or late) 'Timeline' signal, since it's free to do
+ // anything in its process. Just drop the report and move on.
+ return;
+ }
+
+ InputEventTimeline& timeline = it->second;
+ const auto connectionIt = timeline.connectionTimelines.find(connectionToken);
+ if (connectionIt == timeline.connectionTimelines.end()) {
+ timeline.connectionTimelines.emplace(connectionToken, std::move(graphicsTimeline));
+ } else {
+ // Most likely case
+ ConnectionTimeline& connectionTimeline = connectionIt->second;
+ const bool success = connectionTimeline.setGraphicsTimeline(std::move(graphicsTimeline));
+ if (!success) {
+ // We are receiving unreliable data from the app. Just delete the entire connection
+ // timeline for this event
+ timeline.connectionTimelines.erase(connectionIt);
+ }
+ }
+}
+
+/**
+ * We should use the current time 'now()' here to determine the age of the event, but instead we
+ * are using the latest 'eventTime' for efficiency since this time is already acquired, and
+ * 'trackListener' should happen soon after the event occurs.
+ */
+void LatencyTracker::reportAndPruneMatureRecords(nsecs_t newEventTime) {
+ while (!mEventTimes.empty()) {
+ const auto& [oldestEventTime, oldestInputEventId] = *mEventTimes.begin();
+ if (isMatureEvent(oldestEventTime, newEventTime /*now*/)) {
+ // Report and drop this event
+ const auto it = mTimelines.find(oldestInputEventId);
+ LOG_ALWAYS_FATAL_IF(it == mTimelines.end(),
+ "Event %" PRId32 " is in mEventTimes, but not in mTimelines",
+ oldestInputEventId);
+ const InputEventTimeline& timeline = it->second;
+ mTimelineProcessor->processTimeline(timeline);
+ mTimelines.erase(it);
+ mEventTimes.erase(mEventTimes.begin());
+ } else {
+ // If the oldest event does not need to be pruned, no events should be pruned.
+ return;
+ }
+ }
+}
+
+void LatencyTracker::reportNow() {
+ for (const auto& [inputEventId, timeline] : mTimelines) {
+ mTimelineProcessor->processTimeline(timeline);
+ }
+ mTimelines.clear();
+ mEventTimes.clear();
+}
+
+std::string LatencyTracker::dump(const char* prefix) {
+ return StringPrintf("%sLatencyTracker:\n", prefix) +
+ StringPrintf("%s mTimelines.size() = %zu\n", prefix, mTimelines.size()) +
+ StringPrintf("%s mEventTimes.size() = %zu\n", prefix, mEventTimes.size());
+}
+
+} // namespace android::inputdispatcher
diff --git a/services/inputflinger/dispatcher/LatencyTracker.h b/services/inputflinger/dispatcher/LatencyTracker.h
new file mode 100644
index 0000000..289b8ed
--- /dev/null
+++ b/services/inputflinger/dispatcher/LatencyTracker.h
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+#ifndef _UI_INPUT_INPUTDISPATCHER_LATENCYTRACKER_H
+#define _UI_INPUT_INPUTDISPATCHER_LATENCYTRACKER_H
+
+#include <map>
+#include <unordered_map>
+
+#include <binder/IBinder.h>
+#include <input/Input.h>
+
+#include "InputEventTimeline.h"
+
+namespace android::inputdispatcher {
+
+/**
+ * Maintain a record for input events that are received by InputDispatcher, sent out to the apps,
+ * and processed by the apps. Once an event becomes "mature" (older than the ANR timeout), report
+ * the entire input event latency history to the reporting function.
+ *
+ * All calls to LatencyTracker should come from the same thread. It is not thread-safe.
+ */
+class LatencyTracker {
+public:
+ /**
+ * Create a LatencyTracker.
+ * param reportingFunction: the function that will be called in order to report full latency.
+ */
+ LatencyTracker(InputEventTimelineProcessor* processor);
+ /**
+ * Start keeping track of an event identified by inputEventId. This must be called first.
+ */
+ void trackListener(int32_t inputEventId, bool isDown, nsecs_t eventTime, nsecs_t readTime);
+ void trackFinishedEvent(int32_t inputEventId, const sp<IBinder>& connectionToken,
+ nsecs_t deliveryTime, nsecs_t consumeTime, nsecs_t finishTime);
+ void trackGraphicsLatency(int32_t inputEventId, const sp<IBinder>& connectionToken,
+ std::array<nsecs_t, GraphicsTimeline::SIZE> timeline);
+
+ /**
+ * Report all collected events immediately, even if some of them are currently incomplete
+ * and may receive 'trackFinishedEvent' or 'trackGraphicsLatency' calls in the future.
+ * This is useful for tests. Otherwise, tests would have to inject additional "future" events,
+ * which is not convenient.
+ */
+ void reportNow();
+
+ std::string dump(const char* prefix);
+
+private:
+ /**
+ * A collection of InputEventTimelines keyed by inputEventId. An InputEventTimeline is first
+ * created when 'trackListener' is called.
+ * When either 'trackFinishedEvent' or 'trackGraphicsLatency' is called for this input event,
+ * the corresponding InputEventTimeline will be updated for that token.
+ */
+ std::unordered_map<int32_t /*inputEventId*/, InputEventTimeline> mTimelines;
+ /**
+ * The collection of eventTimes will help us quickly find the events that we should prune
+ * from the 'mTimelines'. Since 'mTimelines' is keyed by inputEventId, it would be inefficient
+ * to walk through it directly to find the oldest input events to get rid of.
+ * There is a 1:1 mapping between 'mTimelines' and 'mEventTimes'.
+ * We are using 'multimap' instead of 'map' because there could be more than 1 event with the
+ * same eventTime.
+ */
+ std::multimap<nsecs_t /*eventTime*/, int32_t /*inputEventId*/> mEventTimes;
+
+ InputEventTimelineProcessor* mTimelineProcessor;
+ void reportAndPruneMatureRecords(nsecs_t newEventTime);
+};
+
+} // namespace android::inputdispatcher
+
+#endif // _UI_INPUT_INPUTDISPATCHER_LATENCYTRACKER_H
diff --git a/services/inputflinger/tests/Android.bp b/services/inputflinger/tests/Android.bp
index 42b54c7..918e1be 100644
--- a/services/inputflinger/tests/Android.bp
+++ b/services/inputflinger/tests/Android.bp
@@ -46,6 +46,7 @@
"InputDispatcher_test.cpp",
"InputReader_test.cpp",
"InputFlingerService_test.cpp",
+ "LatencyTracker_test.cpp",
"TestInputListener.cpp",
"UinputDevice.cpp",
],
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index 855453e..93aa6ac 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -441,7 +441,7 @@
sp<FakeInputDispatcherPolicy> mFakePolicy;
sp<InputDispatcher> mDispatcher;
- virtual void SetUp() override {
+ void SetUp() override {
mFakePolicy = new FakeInputDispatcherPolicy();
mDispatcher = new InputDispatcher(mFakePolicy);
mDispatcher->setInputDispatchMode(/*enabled*/ true, /*frozen*/ false);
@@ -449,7 +449,7 @@
ASSERT_EQ(OK, mDispatcher->start());
}
- virtual void TearDown() override {
+ void TearDown() override {
ASSERT_EQ(OK, mDispatcher->stop());
mFakePolicy.clear();
mDispatcher.clear();
diff --git a/services/inputflinger/tests/LatencyTracker_test.cpp b/services/inputflinger/tests/LatencyTracker_test.cpp
new file mode 100644
index 0000000..e7e1937
--- /dev/null
+++ b/services/inputflinger/tests/LatencyTracker_test.cpp
@@ -0,0 +1,253 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+#include "../dispatcher/LatencyTracker.h"
+
+#include <binder/Binder.h>
+#include <gtest/gtest.h>
+#include <inttypes.h>
+#include <log/log.h>
+
+#define TAG "LatencyTracker_test"
+
+using android::inputdispatcher::InputEventTimeline;
+using android::inputdispatcher::LatencyTracker;
+
+namespace android::inputdispatcher {
+
+InputEventTimeline getTestTimeline() {
+ InputEventTimeline t(
+ /*isDown*/ true,
+ /*eventTime*/ 2,
+ /*readTime*/ 3);
+ ConnectionTimeline expectedCT(/*deliveryTime*/ 6, /* consumeTime*/ 7, /*finishTime*/ 8);
+ std::array<nsecs_t, GraphicsTimeline::SIZE> graphicsTimeline;
+ graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME] = 9;
+ graphicsTimeline[GraphicsTimeline::PRESENT_TIME] = 10;
+ expectedCT.setGraphicsTimeline(std::move(graphicsTimeline));
+ t.connectionTimelines.emplace(new BBinder(), std::move(expectedCT));
+ return t;
+}
+
+// --- LatencyTrackerTest ---
+class LatencyTrackerTest : public testing::Test, public InputEventTimelineProcessor {
+protected:
+ std::unique_ptr<LatencyTracker> mTracker;
+ sp<IBinder> connection1;
+ sp<IBinder> connection2;
+
+ void SetUp() override {
+ connection1 = new BBinder();
+ connection2 = new BBinder();
+
+ mTracker = std::make_unique<LatencyTracker>(this);
+ }
+ void TearDown() override {}
+
+ void assertReceivedTimeline(const InputEventTimeline& timeline);
+ /**
+ * Timelines can be received in any order (order is not guaranteed). So if we are expecting more
+ * than 1 timeline, use this function to check that the set of received timelines matches
+ * what we expected.
+ */
+ void assertReceivedTimelines(const std::vector<InputEventTimeline>& timelines);
+
+private:
+ void processTimeline(const InputEventTimeline& timeline) override {
+ mReceivedTimelines.push_back(timeline);
+ }
+ std::deque<InputEventTimeline> mReceivedTimelines;
+};
+
+void LatencyTrackerTest::assertReceivedTimeline(const InputEventTimeline& timeline) {
+ mTracker->reportNow();
+ ASSERT_FALSE(mReceivedTimelines.empty());
+ const InputEventTimeline& t = mReceivedTimelines.front();
+ ASSERT_EQ(timeline, t);
+ mReceivedTimelines.pop_front();
+}
+
+/**
+ * We are essentially comparing two multisets, but without constructing them.
+ * This comparison is inefficient, but it avoids having to construct a set, and also avoids the
+ * declaration of copy constructor for ConnectionTimeline.
+ * We ensure that collections A and B have the same size, that for every element in A, there is an
+ * equal element in B, and for every element in B there is an equal element in A.
+ */
+void LatencyTrackerTest::assertReceivedTimelines(const std::vector<InputEventTimeline>& timelines) {
+ mTracker->reportNow();
+ ASSERT_EQ(timelines.size(), mReceivedTimelines.size());
+ for (const InputEventTimeline& expectedTimeline : timelines) {
+ bool found = false;
+ for (const InputEventTimeline& receivedTimeline : mReceivedTimelines) {
+ if (receivedTimeline == expectedTimeline) {
+ found = true;
+ break;
+ }
+ }
+ ASSERT_TRUE(found) << "Could not find expected timeline with eventTime="
+ << expectedTimeline.eventTime;
+ }
+ for (const InputEventTimeline& receivedTimeline : mReceivedTimelines) {
+ bool found = false;
+ for (const InputEventTimeline& expectedTimeline : timelines) {
+ if (receivedTimeline == expectedTimeline) {
+ found = true;
+ break;
+ }
+ }
+ ASSERT_TRUE(found) << "Could not find received timeline with eventTime="
+ << receivedTimeline.eventTime;
+ }
+ mReceivedTimelines.clear();
+}
+
+/**
+ * Ensure that calling 'trackListener' in isolation only creates an inputflinger timeline, without
+ * any additional ConnectionTimeline's.
+ */
+TEST_F(LatencyTrackerTest, TrackListener_DoesNotTriggerReporting) {
+ mTracker->trackListener(1 /*inputEventId*/, false /*isDown*/, 2 /*eventTime*/, 3 /*readTime*/);
+ assertReceivedTimeline(InputEventTimeline{false, 2, 3});
+}
+
+/**
+ * A single call to trackFinishedEvent should not cause a timeline to be reported.
+ */
+TEST_F(LatencyTrackerTest, TrackFinishedEvent_DoesNotTriggerReporting) {
+ mTracker->trackFinishedEvent(1 /*inputEventId*/, connection1, 2 /*deliveryTime*/,
+ 3 /*consumeTime*/, 4 /*finishTime*/);
+ assertReceivedTimelines({});
+}
+
+/**
+ * A single call to trackGraphicsLatency should not cause a timeline to be reported.
+ */
+TEST_F(LatencyTrackerTest, TrackGraphicsLatency_DoesNotTriggerReporting) {
+ std::array<nsecs_t, GraphicsTimeline::SIZE> graphicsTimeline;
+ graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME] = 2;
+ graphicsTimeline[GraphicsTimeline::PRESENT_TIME] = 3;
+ mTracker->trackGraphicsLatency(1 /*inputEventId*/, connection2, graphicsTimeline);
+ assertReceivedTimelines({});
+}
+
+TEST_F(LatencyTrackerTest, TrackAllParameters_ReportsFullTimeline) {
+ constexpr int32_t inputEventId = 1;
+ InputEventTimeline expected = getTestTimeline();
+
+ const auto& [connectionToken, expectedCT] = *expected.connectionTimelines.begin();
+
+ mTracker->trackListener(inputEventId, expected.isDown, expected.eventTime, expected.readTime);
+ mTracker->trackFinishedEvent(inputEventId, connectionToken, expectedCT.deliveryTime,
+ expectedCT.consumeTime, expectedCT.finishTime);
+ mTracker->trackGraphicsLatency(inputEventId, connectionToken, expectedCT.graphicsTimeline);
+
+ assertReceivedTimeline(expected);
+}
+
+TEST_F(LatencyTrackerTest, MultipleEvents_AreReportedConsistently) {
+ constexpr int32_t inputEventId1 = 1;
+ InputEventTimeline timeline1(
+ /*isDown*/ true,
+ /*eventTime*/ 2,
+ /*readTime*/ 3);
+ timeline1.connectionTimelines.emplace(connection1,
+ ConnectionTimeline(/*deliveryTime*/ 6, /*consumeTime*/ 7,
+ /*finishTime*/ 8));
+ ConnectionTimeline& connectionTimeline1 = timeline1.connectionTimelines.begin()->second;
+ std::array<nsecs_t, GraphicsTimeline::SIZE> graphicsTimeline1;
+ graphicsTimeline1[GraphicsTimeline::GPU_COMPLETED_TIME] = 9;
+ graphicsTimeline1[GraphicsTimeline::PRESENT_TIME] = 10;
+ connectionTimeline1.setGraphicsTimeline(std::move(graphicsTimeline1));
+
+ constexpr int32_t inputEventId2 = 10;
+ InputEventTimeline timeline2(
+ /*isDown*/ false,
+ /*eventTime*/ 20,
+ /*readTime*/ 30);
+ timeline2.connectionTimelines.emplace(connection2,
+ ConnectionTimeline(/*deliveryTime*/ 60,
+ /*consumeTime*/ 70,
+ /*finishTime*/ 80));
+ ConnectionTimeline& connectionTimeline2 = timeline2.connectionTimelines.begin()->second;
+ std::array<nsecs_t, GraphicsTimeline::SIZE> graphicsTimeline2;
+ graphicsTimeline2[GraphicsTimeline::GPU_COMPLETED_TIME] = 90;
+ graphicsTimeline2[GraphicsTimeline::PRESENT_TIME] = 100;
+ connectionTimeline2.setGraphicsTimeline(std::move(graphicsTimeline2));
+
+ // Start processing first event
+ mTracker->trackListener(inputEventId1, timeline1.isDown, timeline1.eventTime,
+ timeline1.readTime);
+ // Start processing second event
+ mTracker->trackListener(inputEventId2, timeline2.isDown, timeline2.eventTime,
+ timeline2.readTime);
+ mTracker->trackFinishedEvent(inputEventId1, connection1, connectionTimeline1.deliveryTime,
+ connectionTimeline1.consumeTime, connectionTimeline1.finishTime);
+
+ mTracker->trackFinishedEvent(inputEventId2, connection2, connectionTimeline2.deliveryTime,
+ connectionTimeline2.consumeTime, connectionTimeline2.finishTime);
+ mTracker->trackGraphicsLatency(inputEventId1, connection1,
+ connectionTimeline1.graphicsTimeline);
+ mTracker->trackGraphicsLatency(inputEventId2, connection2,
+ connectionTimeline2.graphicsTimeline);
+ // Now both events should be completed
+ assertReceivedTimelines({timeline1, timeline2});
+}
+
+/**
+ * Check that LatencyTracker consistently tracks events even if there are many incomplete events.
+ */
+TEST_F(LatencyTrackerTest, IncompleteEvents_AreHandledConsistently) {
+ InputEventTimeline timeline = getTestTimeline();
+ std::vector<InputEventTimeline> expectedTimelines;
+ const ConnectionTimeline& expectedCT = timeline.connectionTimelines.begin()->second;
+ const sp<IBinder>& token = timeline.connectionTimelines.begin()->first;
+
+ for (size_t i = 1; i <= 100; i++) {
+ mTracker->trackListener(i /*inputEventId*/, timeline.isDown, timeline.eventTime,
+ timeline.readTime);
+ expectedTimelines.push_back(
+ InputEventTimeline{timeline.isDown, timeline.eventTime, timeline.readTime});
+ }
+ // Now, complete the first event that was sent.
+ mTracker->trackFinishedEvent(1 /*inputEventId*/, token, expectedCT.deliveryTime,
+ expectedCT.consumeTime, expectedCT.finishTime);
+ mTracker->trackGraphicsLatency(1 /*inputEventId*/, token, expectedCT.graphicsTimeline);
+
+ expectedTimelines[0].connectionTimelines.emplace(token, std::move(expectedCT));
+ assertReceivedTimelines(expectedTimelines);
+}
+
+/**
+ * For simplicity of the implementation, LatencyTracker only starts tracking an event when
+ * 'trackListener' is invoked.
+ * Both 'trackFinishedEvent' and 'trackGraphicsLatency' should not start a new event.
+ * If they are received before 'trackListener' (which should not be possible), they are ignored.
+ */
+TEST_F(LatencyTrackerTest, EventsAreTracked_WhenTrackListenerIsCalledFirst) {
+ constexpr int32_t inputEventId = 1;
+ InputEventTimeline expected = getTestTimeline();
+ const ConnectionTimeline& expectedCT = expected.connectionTimelines.begin()->second;
+ mTracker->trackFinishedEvent(inputEventId, connection1, expectedCT.deliveryTime,
+ expectedCT.consumeTime, expectedCT.finishTime);
+ mTracker->trackGraphicsLatency(inputEventId, connection1, expectedCT.graphicsTimeline);
+
+ mTracker->trackListener(inputEventId, expected.isDown, expected.eventTime, expected.readTime);
+ assertReceivedTimeline(
+ InputEventTimeline{expected.isDown, expected.eventTime, expected.readTime});
+}
+
+} // namespace android::inputdispatcher
diff --git a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h
index dc6eab4..fdcd6ab 100644
--- a/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h
+++ b/services/surfaceflinger/CompositionEngine/include/compositionengine/impl/planner/CachedSet.h
@@ -78,6 +78,7 @@
size_t getDisplayCost() const;
bool hasBufferUpdate() const;
+ bool hasRenderedBuffer() const { return mTexture != nullptr; }
bool hasReadyBuffer() const;
// Decomposes this CachedSet into a vector of its layers as individual CachedSets
diff --git a/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp b/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
index 294ec4b..fdd73af 100644
--- a/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
+++ b/services/surfaceflinger/CompositionEngine/src/planner/Flattener.cpp
@@ -91,7 +91,7 @@
void Flattener::renderCachedSets(renderengine::RenderEngine& renderEngine,
const OutputCompositionState& outputState) {
- if (!mNewCachedSet) {
+ if (!mNewCachedSet || mNewCachedSet->hasRenderedBuffer()) {
return;
}
@@ -393,6 +393,11 @@
return;
}
+ // Don't try to build a new cached set if we already have a new one in progress
+ if (mNewCachedSet) {
+ return;
+ }
+
std::vector<Run> runs = findCandidateRuns(now);
std::optional<Run> bestRun = findBestRun(runs);
diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp
index 488f64d..8f44677 100644
--- a/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/planner/CachedSetTest.cpp
@@ -134,6 +134,7 @@
EXPECT_NE(nullptr, cachedSet.getBuffer());
EXPECT_NE(nullptr, cachedSet.getDrawFence());
EXPECT_TRUE(cachedSet.hasReadyBuffer());
+ EXPECT_TRUE(cachedSet.hasRenderedBuffer());
}
TEST_F(CachedSetTest, createFromLayer) {
diff --git a/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp b/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
index 42096f3..7ec2c98 100644
--- a/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
+++ b/services/surfaceflinger/CompositionEngine/tests/planner/FlattenerTest.cpp
@@ -786,5 +786,45 @@
EXPECT_EQ(overrideBuffer3, overrideBuffer4);
}
+TEST_F(FlattenerTest, flattenLayers_renderCachedSets_doesNotRenderTwice) {
+ auto& layerState1 = mTestLayers[0]->layerState;
+ auto& layerState2 = mTestLayers[1]->layerState;
+ const auto& overrideBuffer1 = layerState1->getOutputLayer()->getState().overrideInfo.buffer;
+ const auto& overrideBuffer2 = layerState2->getOutputLayer()->getState().overrideInfo.buffer;
+
+ const std::vector<const LayerState*> layers = {
+ layerState1.get(),
+ layerState2.get(),
+ };
+
+ initializeFlattener(layers);
+
+ // Mark the layers inactive
+ mTime += 200ms;
+ // layers would be flattened but the buffer would not be overridden
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _, _)).WillOnce(Return(NO_ERROR));
+
+ initializeOverrideBuffer(layers);
+ EXPECT_EQ(getNonBufferHash(layers),
+ mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
+ mFlattener->renderCachedSets(mRenderEngine, mOutputState);
+
+ EXPECT_EQ(nullptr, overrideBuffer1);
+ EXPECT_EQ(nullptr, overrideBuffer2);
+
+ // Simulate attempting to render prior to merging the new cached set with the layer stack.
+ // Here we should not try to re-render.
+ EXPECT_CALL(mRenderEngine, drawLayers(_, _, _, _, _, _)).Times(0);
+ mFlattener->renderCachedSets(mRenderEngine, mOutputState);
+
+ // We provide the override buffer now that it's rendered
+ EXPECT_NE(getNonBufferHash(layers),
+ mFlattener->flattenLayers(layers, getNonBufferHash(layers), mTime));
+ mFlattener->renderCachedSets(mRenderEngine, mOutputState);
+
+ EXPECT_NE(nullptr, overrideBuffer1);
+ EXPECT_EQ(overrideBuffer2, overrideBuffer1);
+}
+
} // namespace
} // namespace android::compositionengine