1-- 2-- Copyright 2020 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-- https://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 16SELECT RUN_METRIC('chrome/chrome_thread_slice.sql'); 17SELECT RUN_METRIC('chrome/scroll_flow_event_queuing_delay.sql'); 18 19-- See b/184134310 why we remove ThreadController active. 20DROP VIEW IF EXISTS blocking_tasks_no_threadcontroller_active; 21CREATE VIEW blocking_tasks_no_threadcontroller_active AS 22SELECT 23 slice.*, 24 ancestor.id AS task_ancestor_id, 25 ancestor.name AS task_ancestor_name 26FROM 27 chrome_thread_slice AS slice LEFT JOIN 28 ancestor_slice(slice.id) AS ancestor ON ancestor.id = slice.parent_id 29WHERE 30 slice.name != "ThreadController active" 31 AND (slice.depth = 0 OR ancestor.name = "ThreadController active"); 32 33-- Sort track ids to optimize joining with slices 34-- as engine doesn't do the sort to join in O(LogN) 35-- per row by default 36-- TODO(243897379): switch this back to a view once we understand why rolling SQLite to 37-- 3.39.2 causes slowdowns. 38DROP TABLE IF EXISTS chrome_annotated_threads_and_processes; 39CREATE TABLE chrome_annotated_threads_and_processes AS 40SELECT 41 thread_track.id AS track_id, 42 chrome_thread.canonical_name AS thread_name, 43 chrome_process.process_type AS process_name 44FROM 45 thread_track JOIN 46 chrome_thread JOIN 47 chrome_process ON 48 thread_track.utid = chrome_thread.utid 49 AND chrome_thread.upid = chrome_process.upid 50ORDER BY 51 track_id ASC; 52 53-- See b/166441398 & crbug/1094361 for why we remove threadpool (originally 54-- the -to-End step). In essence -to-End is often reported on the ThreadPool 55-- after the fact with explicit timestamps so it being blocked isn't noteworthy. 56-- TODO(243897379): switch this back to a view once we understand why rolling SQLite to 57-- 3.39.2 causes slowdowns. 58DROP TABLE IF EXISTS blocking_chrome_tasks_without_threadpool; 59CREATE TABLE blocking_chrome_tasks_without_threadpool AS 60SELECT 61 slice.*, 62 annotations.thread_name AS thread_name, 63 annotations.process_name AS process_name 64FROM 65 blocking_tasks_no_threadcontroller_active AS slice JOIN 66 chrome_annotated_threads_and_processes AS annotations ON 67 annotations.track_id = slice.track_id 68WHERE 69 NOT(thread_name GLOB "*ThreadPool*"); 70 71-- This view grabs any slice that could have prevented any GestureScrollUpdate 72-- flow event from being run (queuing delays). For RunTask we know that its 73-- generic (and thus hard to figure out whats the cause) so we grab the src 74-- location to make it more meaningful. 75-- 76-- See b/184134310 for why we allow depth = 1 and ancestor.id is null (which 77-- implies its a "ThreadController active" slice because we removed it 78-- previously). 79DROP TABLE IF EXISTS blocking_tasks_queuing_delay; 80CREATE TABLE blocking_tasks_queuing_delay AS 81SELECT 82 EXTRACT_ARG(slice.arg_set_id, "task.posted_from.file_name") AS file, 83 EXTRACT_ARG(slice.arg_set_id, "task.posted_from.function_name") AS function, 84 trace_id, 85 queuing_time_ns, 86 avg_vsync_interval, 87 next_track_id, 88 CASE WHEN queuing.ancestor_end <= slice.ts THEN 89 CASE WHEN slice.ts + slice.dur <= queuing.maybe_next_ancestor_ts THEN 90 slice.dur 91 ELSE 92 queuing.maybe_next_ancestor_ts - slice.ts 93 END 94 ELSE 95 CASE WHEN slice.ts + slice.dur <= queuing.maybe_next_ancestor_ts THEN 96 slice.ts + slice.dur - queuing.ancestor_end 97 ELSE 98 queuing.maybe_next_ancestor_ts - queuing.ancestor_end 99 END 100 END AS dur_overlapping_ns, 101 description, 102 scroll_slice_id, 103 scroll_ts, 104 scroll_dur, 105 scroll_track_id, 106 jank, 107 slice.* 108FROM 109 scroll_flow_event_queuing_delay queuing JOIN 110 blocking_chrome_tasks_without_threadpool AS slice ON 111 slice.ts + slice.dur > queuing.ancestor_end 112 AND queuing.maybe_next_ancestor_ts > slice.ts 113 AND slice.track_id = queuing.next_track_id 114WHERE 115 queuing_time_ns IS NOT NULL 116 AND queuing_time_ns > 0; 117 118-- Now for each toplevel task (depth = 0 from above) we want to grab all their 119-- children slices. This is done by joining on descendant_slice which is a 120-- trace processor defined operator. This will results in 1 row for every 121-- descendant slice. So all fields in base.* will be repeated ONCE for each 122-- child, but if it has no slice it will occur only once but all the 123-- |descendant_.*| fields will be NULL because of the LEFT JOIN. 124-- Additionally for mojo events we replace the descendant_name with just the 125-- "interface_name" since that is more descriptive for our jank purposes. 126DROP VIEW IF EXISTS all_descendant_blocking_tasks_queuing_delay; 127CREATE VIEW all_descendant_blocking_tasks_queuing_delay AS 128SELECT 129 descendant.id AS descendant_id, 130 descendant.ts AS descendant_ts, 131 descendant.dur AS descendant_dur, 132 COALESCE( 133 IIF(descendant.arg_set_id IS NOT NULL, 134 EXTRACT_ARG(descendant.arg_set_id, 135 "chrome_mojo_event_info.watcher_notify_interface_tag"), 136 NULL), 137 IIF(descendant.arg_set_id IS NOT NULL, 138 EXTRACT_ARG(descendant.arg_set_id, 139 "chrome_mojo_event_info.mojo_interface_tag"), 140 NULL), 141 descendant.name) AS descendant_name, 142 EXTRACT_ARG(descendant.arg_set_id, 143 "chrome_mojo_event_info.ipc_hash") AS descendant_ipc_hash, 144 descendant.parent_id AS descendant_parent_id, 145 descendant.depth AS descendant_depth, 146 descendant.category AS descendant_category, 147 base.* 148FROM 149 blocking_tasks_queuing_delay base LEFT JOIN 150 descendant_slice(base.id) AS descendant; 151 152DROP TABLE IF EXISTS all_descendant_blocking_tasks_queuing_delay_with_cpu_time; 153CREATE TABLE all_descendant_blocking_tasks_queuing_delay_with_cpu_time AS 154SELECT 155 cpu.thread_dur AS descendant_thread_dur, 156 CAST(cpu.thread_dur AS REAL) / descendant.thread_dur 157 AS descendant_cpu_percentage, 158 CAST(cpu.thread_dur AS REAL) 159 / (descendant.thread_dur 160 / (1 << (descendant.descendant_depth - 1))) > 0.5 161 AS descendant_cpu_time_above_relative_threshold, 162 descendant_dur / descendant.dur AS descendant_dur_percentage, 163 descendant_dur 164 / (descendant.dur / (1 << (descendant.descendant_depth - 1))) > 0.5 165 AS descendant_dur_above_relative_threshold, 166 descendant.* 167FROM 168 all_descendant_blocking_tasks_queuing_delay descendant LEFT JOIN ( 169 SELECT 170 id, thread_dur 171 FROM chrome_thread_slice 172 ) AS cpu ON 173 cpu.id = descendant.descendant_id; 174 175-- Now that we've generated the descendant count how many siblings each row 176-- has. Recall that all the top level tasks are repeated but each row represents 177-- a descendant slice. This means since we LEFT JOIN we will say a slice has 0 178-- siblings if it has no descendants (which is true), and otherwise we will 179-- compute the siblings as the count of all slices with the same parent minus 180-- the current slice. 181DROP VIEW IF EXISTS counted_descendant_blocking_tasks_queuing_delay; 182CREATE VIEW counted_descendant_blocking_tasks_queuing_delay AS 183SELECT 184 base.*, 185 COALESCE(single_descendant.number_of_siblings, 0) AS number_of_siblings 186FROM 187 all_descendant_blocking_tasks_queuing_delay_with_cpu_time base LEFT JOIN ( 188 SELECT 189 descendant_parent_id, 190 COUNT(*) - 1 AS number_of_siblings 191 FROM all_descendant_blocking_tasks_queuing_delay_with_cpu_time 192 WHERE descendant_parent_id IS NOT NULL 193 GROUP BY 1 194 ) single_descendant ON 195 single_descendant.descendant_parent_id = base.descendant_parent_id; 196 197-- Now we group by the |id| which is the top level task id and find the first 198-- descendant_depth where we have a sibling. We need this because we only want 199-- to include single descendant slices in our metric name to keep it easy to 200-- reason about what that code is doing. 201DROP VIEW IF EXISTS blocking_tasks_queuing_delay_with_invalid_depth; 202CREATE VIEW blocking_tasks_queuing_delay_with_invalid_depth AS 203SELECT 204 base.*, 205 ( 206 descendant_cpu_time_above_relative_threshold 207 AND descendant_cpu_percentage > 0.05 208 ) OR ( 209 descendant_dur_above_relative_threshold 210 AND descendant_dur_percentage > 0.05 211 ) AS descendant_major_slice, 212 COALESCE(depth.invalid_depth, 10) AS invalid_depth 213FROM 214 counted_descendant_blocking_tasks_queuing_delay base LEFT JOIN ( 215 SELECT 216 id, 217 MIN(descendant_depth) AS invalid_depth 218 FROM counted_descendant_blocking_tasks_queuing_delay 219 WHERE number_of_siblings >= 1 220 GROUP BY 1 221 ) AS depth ON base.id = depth.id 222ORDER BY 223 descendant_depth ASC, 224 descendant_cpu_percentage DESC, 225 descendant_dur_percentage DESC; 226 227-- Now to get back to a single output per top level task we group by all the 228-- toplevel fields and aggregate the descendant fields. We only include the 229-- descendant if their depth is less than the first depth with siblings (the 230-- |invalid_depth|). 231DROP VIEW IF EXISTS descendant_blocking_tasks_queuing_delay; 232CREATE VIEW descendant_blocking_tasks_queuing_delay AS 233SELECT 234 id, 235 ts, 236 dur, 237 track_id, 238 trace_id, 239 name, 240 category, 241 scroll_slice_id AS scroll_id, 242 scroll_ts, 243 scroll_dur, 244 scroll_track_id, 245 jank, 246 queuing_time_ns, 247 dur_overlapping_ns, 248 description, 249 replace(file, rtrim(file, replace(file, '/', '')), '') AS file, 250 thread_name, 251 process_name, 252 function, 253 avg_vsync_interval, 254 GROUP_CONCAT( 255 CASE WHEN descendant_depth < invalid_depth OR descendant_major_slice THEN 256 descendant_id 257 ELSE 258 NULL 259 END, 260 "-") AS descendant_id, 261 GROUP_CONCAT( 262 CASE WHEN descendant_depth < invalid_depth OR descendant_major_slice THEN 263 descendant_ts 264 ELSE 265 NULL 266 END, 267 "-") AS descendant_ts, 268 GROUP_CONCAT( 269 CASE WHEN descendant_depth < invalid_depth OR descendant_major_slice THEN 270 descendant_dur 271 ELSE 272 NULL 273 END, 274 "-") AS descendant_dur, 275 GROUP_CONCAT( 276 CASE WHEN descendant_depth < invalid_depth OR descendant_major_slice THEN 277 descendant_name 278 ELSE 279 NULL 280 END, "-") AS descendant_name, 281 GROUP_CONCAT( 282 CASE WHEN descendant_depth < invalid_depth OR descendant_major_slice THEN 283 descendant_thread_dur 284 ELSE 285 NULL 286 END, 287 "-") AS descendant_thread_dur, 288 GROUP_CONCAT( 289 CASE WHEN descendant_depth < invalid_depth OR descendant_major_slice THEN 290 descendant_cpu_percentage 291 ELSE 292 NULL 293 END, 294 "-") AS descendant_cpu_time, 295 GROUP_CONCAT( 296 CASE WHEN descendant_category = "mojom" THEN 297 descendant_name 298 ELSE 299 NULL 300 END, 301 "-") AS mojom_name, 302 -- All ipc_hashes should be equal so just select the first non-null one. 303 MIN(descendant_ipc_hash) AS mojom_ipc_hash, 304 GROUP_CONCAT( 305 CASE WHEN 306 descendant_category = "toplevel" 307 AND descendant_name NOT GLOB "*ThreadController*" THEN 308 descendant_name 309 ELSE 310 NULL 311 END, 312 "-") AS toplevel_name, 313 GROUP_CONCAT( 314 CASE WHEN descendant_category = "Java" THEN 315 descendant_name 316 ELSE 317 NULL 318 END, 319 "-") AS java_name 320FROM 321 blocking_tasks_queuing_delay_with_invalid_depth 322GROUP BY 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 323ORDER BY descendant_cpu_percentage DESC; 324 325SELECT CREATE_FUNCTION( 326 -- Function prototype: takes a '-' separated list of slice names (formed by 327 -- the GROUP_CONCAT above) and returns the first slice if any or NULL 328 -- otherwise. 329 'GetFirstSliceNameOrNull(name STRING)', 330 -- Returns the first slice name or NULL 331 'STRING', 332 -- Preforms the actual string modification, takes the either the whole string 333 -- if there is no '-' or up to the first '-'. SUBSTR returns NULL if $name is 334 -- NULL. 335 'SELECT SUBSTR($name, 0, 336 CASE WHEN INSTR($name, "-") = 0 THEN 337 LENGTH($name)+1 ELSE 338 INSTR($name, "-") 339 END)' 340); 341 342SELECT CREATE_FUNCTION( 343 -- Function prototype: takes a '-' separated list of slice names (formed by 344 -- the GROUP_CONCAT above) and checks for certain important view names and 345 -- falls back on GetFirstSliceNameOrNull if it can't find one. 346 'GetJavaSliceSummaryOrNull(name STRING)', 347 -- Returns the summary of the provided list of java slice names. 348 'STRING', 349 -- Performs a bunch of GLOB matches in an order, now there could be multiple 350 -- matches (both Toolbar & TabList could be true) so the order matters in 351 -- tagging since we don't support multiple tagging of values. Ideally we would 352 -- determine which one was the longest duration, but this should be sufficient 353 -- for now. 354 'SELECT 355 CASE WHEN $name GLOB "*ToolbarControlContainer*" THEN 356 "ToolbarControlContainer" 357 WHEN $name GLOB "*ToolbarProgressBar*" THEN 358 "ToolbarProgressBar" 359 WHEN $name GLOB "*TabGroupUiToolbarView*" THEN 360 "TabGroupUiToolbarView" 361 WHEN $name GLOB "*TabGridThumbnailView*" THEN 362 "TabGridThumbnailView" 363 WHEN $name GLOB "*TabGridDialogView*" THEN 364 "TabGridDialogView" 365 WHEN $name GLOB "*BottomContainer*" THEN 366 "BottomContainer" 367 WHEN $name GLOB "*FeedSwipeRefreshLayout*" THEN 368 "FeedSwipeRefreshLayout" 369 WHEN $name GLOB "*AutocompleteEditText*" THEN 370 "AutocompleteEditText" 371 WHEN $name GLOB "*HomeButton*" THEN 372 "HomeButton" 373 WHEN $name GLOB "*ToggleTabStackButton*" THEN 374 "ToggleTabStackButton" 375 WHEN $name GLOB "*ListMenuButton*" THEN 376 "ListMenuButton" 377 WHEN $name GLOB "*ScrimView*" THEN 378 "ScrimView" 379 WHEN $name GLOB "*ChromeImageView*" THEN 380 "ChromeImageView" 381 WHEN $name GLOB "*AppCompatImageView*" THEN 382 "AppCompatImageView" 383 WHEN $name GLOB "*ChromeImageButton*" THEN 384 "ChromeImageButton" 385 WHEN $name GLOB "*AppCompatImageButton*" THEN 386 "AppCompatImageButton" 387 WHEN $name GLOB "*TabListRecyclerView*" THEN 388 "TabListRecyclerView" 389 ELSE 390 GetFirstSliceNameOrNull($name) 391 END' 392); 393 394SELECT CREATE_FUNCTION( 395 -- Function prototype: takes slice name, category and descendant_name and 396 -- determines if this event should be classified as unknown or not. 397 'UnknownEventOrEmptyString(name STRING, cat STRING, has_descendant STRING)', 398 -- Returns either "-UnknownEvent" or "". 399 'STRING', 400 -- If our current event has a posted from we consider it already categorized 401 -- even if we don't have events underneath it. If its java often we won't have 402 -- sub events, and finally if its a single event we just use its name there 403 -- isn't anything under to use so just leave it at that. 404 'SELECT 405 CASE WHEN 406 $name = "ThreadControllerImpl::RunTask" OR 407 $cat = "Java" OR 408 $has_descendant IS NULL THEN 409 "" ELSE 410 "-UnknownEvent" 411 END' 412); 413 414SELECT CREATE_FUNCTION( 415 -- Function prototype: Takes a slice name, function, and file, and determines 416 -- if we should use the slice name, or if its a RunTask event uses the 417 -- function & file name, however if the RunTask posted from is one of the 418 -- simple_watcher paths we collapse them for attributation. 419 'TopLevelName(name STRING, function STRING, file STRING)', 420 'STRING', 421 -- The difference for the mojom functions are: 422 -- 1) PostDispatchNextMessageFromPipe: 423 -- We knew that there is a message in the pipe, didn't try to set up a 424 -- SimpleWatcher to monitor when a new one arrives. 425 -- 2) ArmOrNotify: 426 -- We tried to set up SimpleWatcher, but the setup failed as the 427 -- message arrived as we were setting this up, so we posted a task 428 -- instead. 429 -- 3) Notify: 430 -- SimpleWatcher was set up and after a period of monitoring detected 431 -- a new message. 432 -- For our jank use case this distinction isn't very useful so we group them 433 -- together. 434 'SELECT 435 CASE WHEN $name = "ThreadControllerImpl::RunTask" THEN 436 CASE WHEN $function IN 437 ("PostDispatchNextMessageFromPipe", "ArmOrNotify", "Notify") THEN 438 "posted-from-mojo-pipe" 439 ELSE 440 "posted-from-" || $function || "()-in-" || $file 441 END 442 ELSE 443 $name 444 END' 445); 446 447-- Create a common name for each "cause" based on the slice stack we found. 448DROP VIEW IF EXISTS scroll_jank_cause_queuing_delay_temp; 449CREATE VIEW scroll_jank_cause_queuing_delay_temp AS 450SELECT 451 TopLevelName(name, function, file) || COALESCE( 452 "-" || descendant_name, "") AS location, 453 TopLevelName(name, function, file) || COALESCE( 454 "-" || GetFirstSliceNameOrNull(mojom_name) 455 || COALESCE("(ipc=" || mojom_ipc_hash || ")", ""), 456 "-" || GetFirstSliceNameOrNull(toplevel_name) 457 || COALESCE("(ipc=" || mojom_ipc_hash || ")", ""), 458 "-" || GetJavaSliceSummaryOrNull(java_name), 459 UnknownEventOrEmptyString(name, category, descendant_name) 460 ) AS restricted_location, 461 base.* 462FROM descendant_blocking_tasks_queuing_delay base; 463 464-- Figure out the average time taken during non-janky scrolls updates for each 465-- TraceEvent (metric_name) stack. 466DROP VIEW IF EXISTS scroll_jank_cause_queuing_delay_average_no_jank_time; 467CREATE VIEW scroll_jank_cause_queuing_delay_average_no_jank_time AS 468SELECT 469 location, 470 AVG(dur_overlapping_ns) AS avg_dur_overlapping_ns 471FROM scroll_jank_cause_queuing_delay_temp 472WHERE NOT jank 473GROUP BY 1; 474 475-- Again figure out the average time, but based on a more restricted set of 476-- trace events. 477DROP VIEW IF EXISTS scroll_jank_cause_queuing_delay_average_no_jank_time_restricted; 478CREATE VIEW scroll_jank_cause_queuing_delay_average_no_jank_time_restricted AS 479SELECT 480 restricted_location, 481 AVG(dur_overlapping_ns) AS avg_dur_overlapping_ns_restricted 482FROM scroll_jank_cause_queuing_delay_temp 483WHERE NOT jank 484GROUP BY 1; 485 486 487-- Join every row (jank and non-jank with the average non-jank time for the 488-- given metric_name). 489DROP VIEW IF EXISTS scroll_jank_cause_queuing_delay_unannotated; 490CREATE VIEW scroll_jank_cause_queuing_delay_unannotated AS 491SELECT 492 base.*, 493 'InputLatency.LatencyInfo.Flow.QueuingDelay.' 494 || CASE WHEN jank THEN 'Jank' ELSE 'NoJank' END || '.BlockingTasksUs.' 495 || base.location AS metric_name, 496 COALESCE(avg_no_jank.avg_dur_overlapping_ns, 0) 497 AS avg_no_jank_dur_overlapping_ns 498FROM 499 scroll_jank_cause_queuing_delay_temp base LEFT JOIN 500 scroll_jank_cause_queuing_delay_average_no_jank_time avg_no_jank ON 501 base.location = avg_no_jank.location; 502 503-- Join in the restricted set of trace events average as well to form the final output. 504DROP VIEW IF EXISTS scroll_jank_cause_queuing_delay; 505CREATE VIEW scroll_jank_cause_queuing_delay AS 506SELECT 507 base.*, 508 'QueuingDelay.' 509 || CASE WHEN jank THEN 'Jank' ELSE 'NoJank' END || '.BlockingTasksUs.' 510 || base.restricted_location AS restricted_metric_name, 511 COALESCE(avg_no_jank.avg_dur_overlapping_ns_restricted, 0) 512 AS avg_no_jank_dur_overlapping_ns_restricted 513FROM 514 scroll_jank_cause_queuing_delay_unannotated base LEFT JOIN 515 scroll_jank_cause_queuing_delay_average_no_jank_time_restricted avg_no_jank ON 516 base.restricted_location = avg_no_jank.restricted_location; 517