| Siarhei Vishniakou | a04181f | 2021-03-26 05:56:49 +0000 | [diff] [blame] | 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 "LatencyAggregator" | 
|  | 18 | #include "LatencyAggregator.h" | 
|  | 19 |  | 
|  | 20 | #include <inttypes.h> | 
|  | 21 |  | 
|  | 22 | #include <android-base/stringprintf.h> | 
|  | 23 | #include <input/Input.h> | 
|  | 24 | #include <log/log.h> | 
|  | 25 | #include <server_configurable_flags/get_flags.h> | 
|  | 26 |  | 
|  | 27 | using android::base::StringPrintf; | 
| Siarhei Vishniakou | 99b9d38 | 2021-04-01 08:03:41 +0000 | [diff] [blame] | 28 | using dist_proc::aggregation::KllQuantile; | 
| Siarhei Vishniakou | a04181f | 2021-03-26 05:56:49 +0000 | [diff] [blame] | 29 | using std::chrono_literals::operator""ms; | 
|  | 30 |  | 
| Siarhei Vishniakou | 99b9d38 | 2021-04-01 08:03:41 +0000 | [diff] [blame] | 31 | // Convert the provided nanoseconds into hundreds of microseconds. | 
|  | 32 | // Use hundreds of microseconds (as opposed to microseconds) to preserve space. | 
|  | 33 | static inline int64_t ns2hus(nsecs_t nanos) { | 
|  | 34 | return ns2us(nanos) / 100; | 
|  | 35 | } | 
|  | 36 |  | 
|  | 37 | // The maximum number of events that we will store in the statistics. Any events that we will | 
|  | 38 | // receive after we have reached this number will be ignored. We could also implement this by | 
|  | 39 | // checking the actual size of the current data and making sure that we do not go over. However, | 
|  | 40 | // the serialization process of sketches is too heavy (1 ms for all 14 sketches), and would be too | 
|  | 41 | // much to do (even if infrequently). | 
|  | 42 | // The value here has been determined empirically. | 
|  | 43 | static constexpr size_t MAX_EVENTS_FOR_STATISTICS = 20000; | 
|  | 44 |  | 
| Siarhei Vishniakou | a04181f | 2021-03-26 05:56:49 +0000 | [diff] [blame] | 45 | // Category (=namespace) name for the input settings that are applied at boot time | 
|  | 46 | static const char* INPUT_NATIVE_BOOT = "input_native_boot"; | 
|  | 47 | // Feature flag name for the threshold of end-to-end touch latency that would trigger | 
|  | 48 | // SlowEventReported atom to be pushed | 
|  | 49 | static const char* SLOW_EVENT_MIN_REPORTING_LATENCY_MILLIS = | 
|  | 50 | "slow_event_min_reporting_latency_millis"; | 
|  | 51 | // Feature flag name for the minimum delay before reporting a slow event after having just reported | 
|  | 52 | // a slow event. This helps limit the amount of data sent to the server | 
|  | 53 | static const char* SLOW_EVENT_MIN_REPORTING_INTERVAL_MILLIS = | 
|  | 54 | "slow_event_min_reporting_interval_millis"; | 
|  | 55 |  | 
|  | 56 | // If an event has end-to-end latency > 200 ms, it will get reported as a slow event. | 
|  | 57 | std::chrono::milliseconds DEFAULT_SLOW_EVENT_MIN_REPORTING_LATENCY = 200ms; | 
|  | 58 | // If we receive two slow events less than 1 min apart, we will only report 1 of them. | 
|  | 59 | std::chrono::milliseconds DEFAULT_SLOW_EVENT_MIN_REPORTING_INTERVAL = 60000ms; | 
|  | 60 |  | 
|  | 61 | static std::chrono::milliseconds getSlowEventMinReportingLatency() { | 
|  | 62 | std::string millis = server_configurable_flags:: | 
|  | 63 | GetServerConfigurableFlag(INPUT_NATIVE_BOOT, SLOW_EVENT_MIN_REPORTING_LATENCY_MILLIS, | 
|  | 64 | std::to_string( | 
|  | 65 | DEFAULT_SLOW_EVENT_MIN_REPORTING_LATENCY.count())); | 
|  | 66 | return std::chrono::milliseconds(std::stoi(millis)); | 
|  | 67 | } | 
|  | 68 |  | 
|  | 69 | static std::chrono::milliseconds getSlowEventMinReportingInterval() { | 
|  | 70 | std::string millis = server_configurable_flags:: | 
|  | 71 | GetServerConfigurableFlag(INPUT_NATIVE_BOOT, SLOW_EVENT_MIN_REPORTING_INTERVAL_MILLIS, | 
|  | 72 | std::to_string( | 
|  | 73 | DEFAULT_SLOW_EVENT_MIN_REPORTING_INTERVAL.count())); | 
|  | 74 | return std::chrono::milliseconds(std::stoi(millis)); | 
|  | 75 | } | 
|  | 76 |  | 
|  | 77 | namespace android::inputdispatcher { | 
|  | 78 |  | 
| Siarhei Vishniakou | 99b9d38 | 2021-04-01 08:03:41 +0000 | [diff] [blame] | 79 | /** | 
|  | 80 | * Same as android::util::BytesField, but doesn't store raw pointers, and therefore deletes its | 
|  | 81 | * resources automatically. | 
|  | 82 | */ | 
|  | 83 | class SafeBytesField { | 
|  | 84 | public: | 
|  | 85 | explicit SafeBytesField(dist_proc::aggregation::KllQuantile& quantile) { | 
|  | 86 | const zetasketch::android::AggregatorStateProto aggProto = quantile.SerializeToProto(); | 
|  | 87 | mBuffer.resize(aggProto.ByteSizeLong()); | 
|  | 88 | aggProto.SerializeToArray(mBuffer.data(), mBuffer.size()); | 
|  | 89 | } | 
|  | 90 | android::util::BytesField getBytesField() { | 
|  | 91 | return android::util::BytesField(mBuffer.data(), mBuffer.size()); | 
|  | 92 | } | 
| Siarhei Vishniakou | a04181f | 2021-03-26 05:56:49 +0000 | [diff] [blame] | 93 |  | 
| Siarhei Vishniakou | 99b9d38 | 2021-04-01 08:03:41 +0000 | [diff] [blame] | 94 | private: | 
|  | 95 | std::vector<char> mBuffer; | 
|  | 96 | }; | 
| Siarhei Vishniakou | a04181f | 2021-03-26 05:56:49 +0000 | [diff] [blame] | 97 |  | 
|  | 98 | LatencyAggregator::LatencyAggregator() { | 
|  | 99 | AStatsManager_setPullAtomCallback(android::util::INPUT_EVENT_LATENCY_SKETCH, nullptr, | 
|  | 100 | LatencyAggregator::pullAtomCallback, this); | 
| Siarhei Vishniakou | 99b9d38 | 2021-04-01 08:03:41 +0000 | [diff] [blame] | 101 | dist_proc::aggregation::KllQuantileOptions options; | 
|  | 102 | options.set_inv_eps(100); // Request precision of 1.0%, instead of default 0.1% | 
|  | 103 | for (size_t i = 0; i < SketchIndex::SIZE; i++) { | 
|  | 104 | mDownSketches[i] = KllQuantile::Create(options); | 
|  | 105 | mMoveSketches[i] = KllQuantile::Create(options); | 
|  | 106 | } | 
| Siarhei Vishniakou | a04181f | 2021-03-26 05:56:49 +0000 | [diff] [blame] | 107 | } | 
|  | 108 |  | 
|  | 109 | LatencyAggregator::~LatencyAggregator() { | 
|  | 110 | AStatsManager_clearPullAtomCallback(android::util::INPUT_EVENT_LATENCY_SKETCH); | 
|  | 111 | } | 
|  | 112 |  | 
|  | 113 | AStatsManager_PullAtomCallbackReturn LatencyAggregator::pullAtomCallback(int32_t atomTag, | 
|  | 114 | AStatsEventList* data, | 
|  | 115 | void* cookie) { | 
|  | 116 | LatencyAggregator* pAggregator = reinterpret_cast<LatencyAggregator*>(cookie); | 
|  | 117 | if (pAggregator == nullptr) { | 
|  | 118 | LOG_ALWAYS_FATAL("pAggregator is null!"); | 
|  | 119 | } | 
|  | 120 | return pAggregator->pullData(data); | 
|  | 121 | } | 
|  | 122 |  | 
|  | 123 | void LatencyAggregator::processTimeline(const InputEventTimeline& timeline) { | 
|  | 124 | processStatistics(timeline); | 
|  | 125 | processSlowEvent(timeline); | 
|  | 126 | } | 
|  | 127 |  | 
|  | 128 | void LatencyAggregator::processStatistics(const InputEventTimeline& timeline) { | 
| Siarhei Vishniakou | 99b9d38 | 2021-04-01 08:03:41 +0000 | [diff] [blame] | 129 | // Before we do any processing, check that we have not yet exceeded MAX_SIZE | 
|  | 130 | if (mNumSketchEventsProcessed >= MAX_EVENTS_FOR_STATISTICS) { | 
|  | 131 | return; | 
|  | 132 | } | 
|  | 133 | mNumSketchEventsProcessed++; | 
|  | 134 |  | 
|  | 135 | std::array<std::unique_ptr<KllQuantile>, SketchIndex::SIZE>& sketches = | 
| Siarhei Vishniakou | a04181f | 2021-03-26 05:56:49 +0000 | [diff] [blame] | 136 | timeline.isDown ? mDownSketches : mMoveSketches; | 
|  | 137 |  | 
|  | 138 | // Process common ones first | 
|  | 139 | const nsecs_t eventToRead = timeline.readTime - timeline.eventTime; | 
| Siarhei Vishniakou | 99b9d38 | 2021-04-01 08:03:41 +0000 | [diff] [blame] | 140 | sketches[SketchIndex::EVENT_TO_READ]->Add(ns2hus(eventToRead)); | 
| Siarhei Vishniakou | a04181f | 2021-03-26 05:56:49 +0000 | [diff] [blame] | 141 |  | 
|  | 142 | // Now process per-connection ones | 
|  | 143 | for (const auto& [connectionToken, connectionTimeline] : timeline.connectionTimelines) { | 
|  | 144 | if (!connectionTimeline.isComplete()) { | 
|  | 145 | continue; | 
|  | 146 | } | 
|  | 147 | const nsecs_t readToDeliver = connectionTimeline.deliveryTime - timeline.readTime; | 
|  | 148 | const nsecs_t deliverToConsume = | 
|  | 149 | connectionTimeline.consumeTime - connectionTimeline.deliveryTime; | 
|  | 150 | const nsecs_t consumeToFinish = | 
|  | 151 | connectionTimeline.finishTime - connectionTimeline.consumeTime; | 
|  | 152 | const nsecs_t gpuCompletedTime = | 
|  | 153 | connectionTimeline.graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME]; | 
|  | 154 | const nsecs_t presentTime = | 
|  | 155 | connectionTimeline.graphicsTimeline[GraphicsTimeline::PRESENT_TIME]; | 
|  | 156 | const nsecs_t consumeToGpuComplete = gpuCompletedTime - connectionTimeline.consumeTime; | 
|  | 157 | const nsecs_t gpuCompleteToPresent = presentTime - gpuCompletedTime; | 
|  | 158 | const nsecs_t endToEnd = presentTime - timeline.eventTime; | 
|  | 159 |  | 
| Siarhei Vishniakou | 99b9d38 | 2021-04-01 08:03:41 +0000 | [diff] [blame] | 160 | sketches[SketchIndex::READ_TO_DELIVER]->Add(ns2hus(readToDeliver)); | 
|  | 161 | sketches[SketchIndex::DELIVER_TO_CONSUME]->Add(ns2hus(deliverToConsume)); | 
|  | 162 | sketches[SketchIndex::CONSUME_TO_FINISH]->Add(ns2hus(consumeToFinish)); | 
|  | 163 | sketches[SketchIndex::CONSUME_TO_GPU_COMPLETE]->Add(ns2hus(consumeToGpuComplete)); | 
|  | 164 | sketches[SketchIndex::GPU_COMPLETE_TO_PRESENT]->Add(ns2hus(gpuCompleteToPresent)); | 
|  | 165 | sketches[SketchIndex::END_TO_END]->Add(ns2hus(endToEnd)); | 
| Siarhei Vishniakou | a04181f | 2021-03-26 05:56:49 +0000 | [diff] [blame] | 166 | } | 
|  | 167 | } | 
|  | 168 |  | 
|  | 169 | AStatsManager_PullAtomCallbackReturn LatencyAggregator::pullData(AStatsEventList* data) { | 
| Siarhei Vishniakou | 99b9d38 | 2021-04-01 08:03:41 +0000 | [diff] [blame] | 170 | std::array<std::unique_ptr<SafeBytesField>, SketchIndex::SIZE> serializedDownData; | 
|  | 171 | std::array<std::unique_ptr<SafeBytesField>, SketchIndex::SIZE> serializedMoveData; | 
|  | 172 | for (size_t i = 0; i < SketchIndex::SIZE; i++) { | 
|  | 173 | serializedDownData[i] = std::make_unique<SafeBytesField>(*mDownSketches[i]); | 
|  | 174 | serializedMoveData[i] = std::make_unique<SafeBytesField>(*mMoveSketches[i]); | 
|  | 175 | } | 
|  | 176 | android::util:: | 
|  | 177 | addAStatsEvent(data, android::util::INPUT_EVENT_LATENCY_SKETCH, | 
|  | 178 | // DOWN sketches | 
|  | 179 | serializedDownData[SketchIndex::EVENT_TO_READ]->getBytesField(), | 
|  | 180 | serializedDownData[SketchIndex::READ_TO_DELIVER]->getBytesField(), | 
|  | 181 | serializedDownData[SketchIndex::DELIVER_TO_CONSUME]->getBytesField(), | 
|  | 182 | serializedDownData[SketchIndex::CONSUME_TO_FINISH]->getBytesField(), | 
|  | 183 | serializedDownData[SketchIndex::CONSUME_TO_GPU_COMPLETE] | 
|  | 184 | ->getBytesField(), | 
|  | 185 | serializedDownData[SketchIndex::GPU_COMPLETE_TO_PRESENT] | 
|  | 186 | ->getBytesField(), | 
|  | 187 | serializedDownData[SketchIndex::END_TO_END]->getBytesField(), | 
|  | 188 | // MOVE sketches | 
|  | 189 | serializedMoveData[SketchIndex::EVENT_TO_READ]->getBytesField(), | 
|  | 190 | serializedMoveData[SketchIndex::READ_TO_DELIVER]->getBytesField(), | 
|  | 191 | serializedMoveData[SketchIndex::DELIVER_TO_CONSUME]->getBytesField(), | 
|  | 192 | serializedMoveData[SketchIndex::CONSUME_TO_FINISH]->getBytesField(), | 
|  | 193 | serializedMoveData[SketchIndex::CONSUME_TO_GPU_COMPLETE] | 
|  | 194 | ->getBytesField(), | 
|  | 195 | serializedMoveData[SketchIndex::GPU_COMPLETE_TO_PRESENT] | 
|  | 196 | ->getBytesField(), | 
|  | 197 | serializedMoveData[SketchIndex::END_TO_END]->getBytesField()); | 
| Siarhei Vishniakou | a04181f | 2021-03-26 05:56:49 +0000 | [diff] [blame] | 198 |  | 
|  | 199 | for (size_t i = 0; i < SketchIndex::SIZE; i++) { | 
| Siarhei Vishniakou | 99b9d38 | 2021-04-01 08:03:41 +0000 | [diff] [blame] | 200 | mDownSketches[i]->Reset(); | 
|  | 201 | mMoveSketches[i]->Reset(); | 
| Siarhei Vishniakou | a04181f | 2021-03-26 05:56:49 +0000 | [diff] [blame] | 202 | } | 
| Siarhei Vishniakou | 99b9d38 | 2021-04-01 08:03:41 +0000 | [diff] [blame] | 203 | // Start new aggregations | 
|  | 204 | mNumSketchEventsProcessed = 0; | 
| Siarhei Vishniakou | a04181f | 2021-03-26 05:56:49 +0000 | [diff] [blame] | 205 | return AStatsManager_PULL_SUCCESS; | 
|  | 206 | } | 
|  | 207 |  | 
|  | 208 | void LatencyAggregator::processSlowEvent(const InputEventTimeline& timeline) { | 
|  | 209 | static const std::chrono::duration sSlowEventThreshold = getSlowEventMinReportingLatency(); | 
|  | 210 | static const std::chrono::duration sSlowEventReportingInterval = | 
|  | 211 | getSlowEventMinReportingInterval(); | 
|  | 212 | for (const auto& [token, connectionTimeline] : timeline.connectionTimelines) { | 
|  | 213 | if (!connectionTimeline.isComplete()) { | 
|  | 214 | continue; | 
|  | 215 | } | 
|  | 216 | mNumEventsSinceLastSlowEventReport++; | 
|  | 217 | const nsecs_t presentTime = | 
|  | 218 | connectionTimeline.graphicsTimeline[GraphicsTimeline::PRESENT_TIME]; | 
|  | 219 | const std::chrono::nanoseconds endToEndLatency = | 
|  | 220 | std::chrono::nanoseconds(presentTime - timeline.eventTime); | 
|  | 221 | if (endToEndLatency < sSlowEventThreshold) { | 
|  | 222 | continue; | 
|  | 223 | } | 
|  | 224 | // This is a slow event. Before we report it, check if we are reporting too often | 
|  | 225 | const std::chrono::duration elapsedSinceLastReport = | 
|  | 226 | std::chrono::nanoseconds(timeline.eventTime - mLastSlowEventTime); | 
|  | 227 | if (elapsedSinceLastReport < sSlowEventReportingInterval) { | 
|  | 228 | mNumSkippedSlowEvents++; | 
|  | 229 | continue; | 
|  | 230 | } | 
|  | 231 |  | 
|  | 232 | const nsecs_t eventToRead = timeline.readTime - timeline.eventTime; | 
|  | 233 | const nsecs_t readToDeliver = connectionTimeline.deliveryTime - timeline.readTime; | 
|  | 234 | const nsecs_t deliverToConsume = | 
|  | 235 | connectionTimeline.consumeTime - connectionTimeline.deliveryTime; | 
|  | 236 | const nsecs_t consumeToFinish = | 
|  | 237 | connectionTimeline.finishTime - connectionTimeline.consumeTime; | 
|  | 238 | const nsecs_t gpuCompletedTime = | 
|  | 239 | connectionTimeline.graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME]; | 
|  | 240 | const nsecs_t consumeToGpuComplete = gpuCompletedTime - connectionTimeline.consumeTime; | 
|  | 241 | const nsecs_t gpuCompleteToPresent = presentTime - gpuCompletedTime; | 
|  | 242 |  | 
|  | 243 | android::util::stats_write(android::util::SLOW_INPUT_EVENT_REPORTED, timeline.isDown, | 
|  | 244 | static_cast<int32_t>(ns2us(eventToRead)), | 
|  | 245 | static_cast<int32_t>(ns2us(readToDeliver)), | 
|  | 246 | static_cast<int32_t>(ns2us(deliverToConsume)), | 
|  | 247 | static_cast<int32_t>(ns2us(consumeToFinish)), | 
|  | 248 | static_cast<int32_t>(ns2us(consumeToGpuComplete)), | 
|  | 249 | static_cast<int32_t>(ns2us(gpuCompleteToPresent)), | 
|  | 250 | static_cast<int32_t>(ns2us(endToEndLatency.count())), | 
|  | 251 | static_cast<int32_t>(mNumEventsSinceLastSlowEventReport), | 
|  | 252 | static_cast<int32_t>(mNumSkippedSlowEvents)); | 
|  | 253 | mNumEventsSinceLastSlowEventReport = 0; | 
|  | 254 | mNumSkippedSlowEvents = 0; | 
|  | 255 | mLastSlowEventTime = timeline.readTime; | 
|  | 256 | } | 
|  | 257 | } | 
|  | 258 |  | 
|  | 259 | std::string LatencyAggregator::dump(const char* prefix) { | 
| Siarhei Vishniakou | 99b9d38 | 2021-04-01 08:03:41 +0000 | [diff] [blame] | 260 | std::string sketchDump = StringPrintf("%s  Sketches:\n", prefix); | 
|  | 261 | for (size_t i = 0; i < SketchIndex::SIZE; i++) { | 
|  | 262 | const int64_t numDown = mDownSketches[i]->num_values(); | 
|  | 263 | SafeBytesField downBytesField(*mDownSketches[i]); | 
|  | 264 | const float downBytesKb = downBytesField.getBytesField().arg_length * 1E-3; | 
|  | 265 | const int64_t numMove = mMoveSketches[i]->num_values(); | 
|  | 266 | SafeBytesField moveBytesField(*mMoveSketches[i]); | 
|  | 267 | const float moveBytesKb = moveBytesField.getBytesField().arg_length * 1E-3; | 
|  | 268 | sketchDump += | 
|  | 269 | StringPrintf("%s    mDownSketches[%zu]->num_values = %" PRId64 " size = %.1fKB" | 
|  | 270 | " mMoveSketches[%zu]->num_values = %" PRId64 " size = %.1fKB\n", | 
|  | 271 | prefix, i, numDown, downBytesKb, i, numMove, moveBytesKb); | 
|  | 272 | } | 
|  | 273 |  | 
|  | 274 | return StringPrintf("%sLatencyAggregator:\n", prefix) + sketchDump + | 
|  | 275 | StringPrintf("%s  mNumSketchEventsProcessed=%zu\n", prefix, mNumSketchEventsProcessed) + | 
|  | 276 | StringPrintf("%s  mLastSlowEventTime=%" PRId64 "\n", prefix, mLastSlowEventTime) + | 
|  | 277 | StringPrintf("%s  mNumEventsSinceLastSlowEventReport = %zu\n", prefix, | 
| Siarhei Vishniakou | a04181f | 2021-03-26 05:56:49 +0000 | [diff] [blame] | 278 | mNumEventsSinceLastSlowEventReport) + | 
| Siarhei Vishniakou | 99b9d38 | 2021-04-01 08:03:41 +0000 | [diff] [blame] | 279 | StringPrintf("%s  mNumSkippedSlowEvents = %zu\n", prefix, mNumSkippedSlowEvents); | 
| Siarhei Vishniakou | a04181f | 2021-03-26 05:56:49 +0000 | [diff] [blame] | 280 | } | 
|  | 281 |  | 
|  | 282 | } // namespace android::inputdispatcher |