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