1-- 2-- Copyright 2024 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 16-- Returns an instance of `RawMarkerTable` as defined in 17-- https://github.com/firefox-devtools/profiler/blob/main/src/types/profile.js 18CREATE PERFETTO FUNCTION _export_firefox_thread_markers() 19RETURNS STRING AS 20SELECT 21 json_object( 22 'data', json_array(), 23 'name', json_array(), 24 'startTime', json_array(), 25 'endTime', json_array(), 26 'phase', json_array(), 27 'category', json_array(), 28 -- threadId?: Tid[] 29 'length', 0 30 ); 31 32-- Returns an empty instance of `NativeSymbolTable` as defined in 33-- https://github.com/firefox-devtools/profiler/blob/main/src/types/profile.js 34CREATE PERFETTO FUNCTION _export_firefox_native_symbol_table() 35RETURNS STRING AS 36SELECT 37 json_object( 38 'libIndex', json_array(), 39 'address', json_array(), 40 'name', json_array(), 41 'functionSize', json_array(), 42 'length', 0 43 ); 44 45-- Returns an empty instance of `ResourceTable` as defined in 46-- https://github.com/firefox-devtools/profiler/blob/main/src/types/profile.js 47CREATE PERFETTO FUNCTION _export_firefox_resource_table() 48RETURNS STRING AS 49SELECT 50 json_object( 51 'length', 0, 52 'lib', json_array(), 53 'name', json_array(), 54 'host', json_array(), 55 'type', json_array() 56 ); 57 58-- Materialize this intermediate table and sort by `callsite_id` to speedup the 59-- generation of the stack_table further down. 60CREATE PERFETTO TABLE _export_to_firefox_table AS 61WITH 62 symbol AS ( 63 SELECT 64 symbol_set_id, 65 rank() OVER (PARTITION BY symbol_set_id ORDER BY id DESC RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING) - 1 AS inline_depth, 66 count() OVER (PARTITION BY symbol_set_id) - 1 AS max_inline_depth, 67 name 68 FROM stack_profile_symbol 69 ), 70 callsite_base AS ( 71 SELECT 72 id, 73 parent_id, 74 name, 75 symbol_set_id, 76 iif(inline_count = 0, 0, inline_count - 1) AS max_inline_depth 77 FROM ( 78 SELECT 79 spc.id, 80 spc.parent_id, 81 coalesce(s.name, spf.name, '') AS name, 82 sfp.symbol_set_id, 83 ( 84 SELECT 85 count(*) 86 FROM stack_profile_symbol AS s 87 WHERE 88 s.symbol_set_id = sfp.symbol_set_id 89 ) AS inline_count 90 FROM stack_profile_callsite AS spc 91 JOIN stack_profile_frame AS spf 92 ON ( 93 spc.frame_id = spf.id 94 ) 95 ) 96 ), 97 callsite_recursive AS ( 98 SELECT 99 s.utid, 100 spc.id, 101 spc.parent_id, 102 spc.frame_id 103 FROM ( 104 SELECT DISTINCT 105 callsite_id, 106 utid 107 FROM perf_sample 108 ) AS s 109 JOIN stack_profile_callsite AS spc 110 ON spc.id = s.callsite_id 111 UNION ALL 112 SELECT 113 child.utid, 114 parent.id, 115 parent.parent_id, 116 parent.frame_id 117 FROM callsite_recursive AS child 118 JOIN stack_profile_callsite AS parent 119 ON child.parent_id = parent.id 120 ), 121 unique_callsite AS ( 122 SELECT DISTINCT 123 * 124 FROM callsite_recursive 125 ), 126 expanded_callsite AS ( 127 SELECT 128 c.utid, 129 c.id, 130 c.parent_id, 131 c.frame_id, 132 coalesce(s.name, spf.name, '') AS name, 133 coalesce(s.inline_depth, 0) AS inline_depth, 134 coalesce(s.max_inline_depth, 0) AS max_inline_depth 135 FROM unique_callsite AS c 136 JOIN stack_profile_frame AS spf 137 ON ( 138 c.frame_id = spf.id 139 ) 140 LEFT JOIN symbol AS s 141 USING (symbol_set_id) 142 ) 143SELECT 144 utid, 145 id AS callsite_id, 146 parent_id AS parent_callsite_id, 147 name, 148 inline_depth, 149 inline_depth = max_inline_depth AS is_most_inlined, 150 dense_rank() OVER (PARTITION BY utid ORDER BY id, inline_depth) - 1 AS stack_table_index, 151 dense_rank() OVER (PARTITION BY utid ORDER BY frame_id, inline_depth) - 1 AS frame_table_index, 152 dense_rank() OVER (PARTITION BY utid ORDER BY name) - 1 AS func_table_index, 153 dense_rank() OVER (PARTITION BY utid ORDER BY name) - 1 AS string_table_index 154FROM expanded_callsite 155ORDER BY 156 utid, 157 id; 158 159-- Returns an instance of `SamplesTable` as defined in 160-- https://github.com/firefox-devtools/profiler/blob/main/src/types/profile.js 161-- for the given `utid`. 162CREATE PERFETTO FUNCTION _export_firefox_samples_table( 163 utid JOINID(thread.id) 164) 165RETURNS STRING AS 166WITH 167 samples_table AS ( 168 SELECT 169 row_number() OVER (ORDER BY s.id) - 1 AS idx, 170 s.ts AS time, 171 t.stack_table_index AS stack 172 FROM perf_sample AS s 173 JOIN _export_to_firefox_table AS t 174 USING (utid, callsite_id) 175 WHERE 176 utid = $utid AND t.is_most_inlined 177 ) 178SELECT 179 json_object( 180 -- responsiveness?: Array<?Milliseconds> 181 -- eventDelay?: Array<?Milliseconds> 182 'stack', json_group_array(stack ORDER BY idx), 183 'time', json_group_array(time ORDER BY idx), 184 'weight', NULL, 185 'weightType', 'samples', 186 -- threadCPUDelta?: Array<number | null> 187 -- threadId?: Tid[] 188 'length', count(*) 189 ) 190FROM samples_table; 191 192-- Returns an instance of `StackTable` as defined in 193-- https://github.com/firefox-devtools/profiler/blob/main/src/types/profile.js 194-- for the given `utid`. 195CREATE PERFETTO FUNCTION _export_firefox_stack_table( 196 utid JOINID(thread.id) 197) 198RETURNS STRING AS 199WITH 200 parent AS ( 201 SELECT 202 * 203 FROM _export_to_firefox_table 204 WHERE 205 utid = $utid 206 ), 207 stack_table AS ( 208 SELECT 209 stack_table_index AS idx, 210 frame_table_index AS frame, 211 0 AS category, 212 0 AS subcategory, 213 -- It is key that this lookup is fast. That is why we have materialized 214 -- the `_export_to_firefox_table` table and sorted it by `utid` and 215 -- `callsite_id`. 216 iif( 217 child.inline_depth = 0, 218 ( 219 SELECT 220 stack_table_index 221 FROM parent 222 WHERE 223 child.parent_callsite_id = parent.callsite_id AND parent.is_most_inlined 224 ), 225 ( 226 SELECT 227 stack_table_index 228 FROM parent 229 WHERE 230 child.callsite_id = parent.callsite_id 231 AND child.inline_depth - 1 = parent.inline_depth 232 ) 233 ) AS prefix 234 FROM _export_to_firefox_table AS child 235 WHERE 236 child.utid = $utid 237 ) 238SELECT 239 json_object( 240 'frame', json_group_array(frame ORDER BY idx), 241 'category', json_group_array(category ORDER BY idx), 242 'subcategory', json_group_array(subcategory ORDER BY idx), 243 'prefix', json_group_array(prefix ORDER BY idx), 244 'length', count(*) 245 ) 246FROM stack_table; 247 248-- Returns an instance of `FrameTable` as defined in 249-- https://github.com/firefox-devtools/profiler/blob/main/src/types/profile.js 250-- for the given `utid`. 251CREATE PERFETTO FUNCTION _export_firefox_frame_table( 252 utid JOINID(thread.id) 253) 254RETURNS STRING AS 255WITH 256 frame_table AS ( 257 SELECT DISTINCT 258 frame_table_index AS idx, 259 -1 AS address, 260 inline_depth, 261 0 AS category, 262 0 AS subcategory, 263 func_table_index AS func, 264 NULL AS native_symbol, 265 NULL AS inner_window_id, 266 NULL AS implementation, 267 NULL AS line, 268 NULL AS column 269 FROM _export_to_firefox_table 270 WHERE 271 utid = $utid 272 ) 273SELECT 274 json_object( 275 'address', json_group_array(address ORDER BY idx), 276 'inlineDepth', json_group_array(inline_depth ORDER BY idx), 277 'category', json_group_array(category ORDER BY idx), 278 'subcategory', json_group_array(subcategory ORDER BY idx), 279 'func', json_group_array(func ORDER BY idx), 280 'nativeSymbol', json_group_array(native_symbol ORDER BY idx), 281 'innerWindowID', json_group_array(inner_window_id ORDER BY idx), 282 'implementation', json_group_array(implementation ORDER BY idx), 283 'line', json_group_array(line ORDER BY idx), 284 'column', json_group_array(column ORDER BY idx), 285 'length', count(*) 286 ) 287FROM frame_table; 288 289-- Returns an array of strings for the given `utid`. 290CREATE PERFETTO FUNCTION _export_firefox_string_array( 291 utid JOINID(thread.id) 292) 293RETURNS STRING AS 294WITH 295 string_table AS ( 296 SELECT DISTINCT 297 string_table_index AS idx, 298 name AS str 299 FROM _export_to_firefox_table 300 WHERE 301 utid = $utid 302 ) 303SELECT 304 json_group_array(str ORDER BY idx) 305FROM string_table; 306 307-- Returns an instance of `FuncTable` as defined in 308-- https://github.com/firefox-devtools/profiler/blob/main/src/types/profile.js 309-- for the given `utid`. 310CREATE PERFETTO FUNCTION _export_firefox_func_table( 311 utid JOINID(thread.id) 312) 313RETURNS STRING AS 314WITH 315 func_table AS ( 316 SELECT DISTINCT 317 func_table_index AS idx, 318 string_table_index AS name, 319 FALSE AS is_js, 320 FALSE AS relevant_for_js, 321 -1 AS resource, 322 NULL AS file_name, 323 NULL AS line_number, 324 NULL AS column_number 325 FROM _export_to_firefox_table 326 WHERE 327 utid = $utid 328 ) 329SELECT 330 json_object( 331 'name', json_group_array(name ORDER BY idx), 332 'isJS', json_group_array(is_js ORDER BY idx), 333 'relevantForJS', json_group_array(relevant_for_js ORDER BY idx), 334 'resource', json_group_array(resource ORDER BY idx), 335 'fileName', json_group_array(file_name ORDER BY idx), 336 'lineNumber', json_group_array(line_number ORDER BY idx), 337 'columnNumber', json_group_array(column_number ORDER BY idx), 338 'length', count(*) 339 ) 340FROM func_table; 341 342-- Returns an instance of `Thread` as defined in 343-- https://github.com/firefox-devtools/profiler/blob/main/src/types/profile.js 344-- for the given `utid`. 345CREATE PERFETTO FUNCTION _export_firefox_thread( 346 utid JOINID(thread.id) 347) 348RETURNS STRING AS 349SELECT 350 -- jsTracer?: JsTracerTable 351 -- isPrivateBrowsing?: boolean 352 -- userContextId?: number 353 json_object( 354 'processType', 'default', 355 -- processStartupTime: Milliseconds 356 -- processShutdownTime: Milliseconds | null 357 -- registerTime: Milliseconds 358 -- unregisterTime: Milliseconds | null 359 -- pausedRanges: PausedRange[] 360 -- showMarkersInTimeline?: boolean 361 'name', coalesce(thread.name, ''), 362 'isMainThread', FALSE, 363 -- 'eTLD+1'?: string 364 -- processName?: string 365 -- isJsTracer?: boolean 366 'pid', coalesce(process.pid, 0), 367 'tid', coalesce(thread.tid, 0), 368 'samples', json(_export_firefox_samples_table($utid)), 369 -- jsAllocations?: JsAllocationsTable 370 -- nativeAllocations?: NativeAllocationsTable 371 'markers', json(_export_firefox_thread_markers()), 372 'stackTable', json(_export_firefox_stack_table($utid)), 373 'frameTable', json(_export_firefox_frame_table($utid)), 374 'stringArray', json(_export_firefox_string_array($utid)), 375 'funcTable', json(_export_firefox_func_table($utid)), 376 'resourceTable', json(_export_firefox_resource_table()), 377 'nativeSymbols', json(_export_firefox_native_symbol_table()) 378 ) 379FROM thread 380JOIN process 381 USING (upid) 382WHERE 383 utid = $utid; 384 385-- Returns an array of `Thread` instances as defined in 386-- https://github.com/firefox-devtools/profiler/blob/main/src/types/profile.js 387-- for each thread present in the trace. 388CREATE PERFETTO FUNCTION _export_firefox_threads() 389RETURNS STRING AS 390SELECT 391 json_group_array(json(_export_firefox_thread(utid))) 392FROM thread; 393 394-- Returns an instance of `ProfileMeta` as defined in 395-- https://github.com/firefox-devtools/profiler/blob/main/src/types/profile.js 396CREATE PERFETTO FUNCTION _export_firefox_meta() 397RETURNS STRING AS 398SELECT 399 -- device?: string 400 -- importedFrom?: string 401 -- usesOnlyOneStackType?: boolean 402 -- doesNotUseFrameImplementation?: boolean 403 -- sourceCodeIsNotOnSearchfox?: boolean 404 -- extra?: ExtraProfileInfoSection[] 405 -- initialVisibleThreads?: ThreadIndex[] 406 -- initialSelectedThreads?: ThreadIndex[] 407 -- keepProfileThreadOrder?: boolean 408 -- gramsOfCO2ePerKWh?: number 409 json_object( 410 'interval', 1, 411 'startTime', 0, 412 -- default 413 -- endTime?: Milliseconds 414 -- profilingStartTime?: Milliseconds 415 -- profilingEndTime?: Milliseconds 416 'processType', 0, 417 -- extensions?: ExtensionTable 418 'categories', json_array( 419 json_object('name', 'Other', 'color', 'grey', 'subcategories', json_array('Other')) 420 ), 421 'product', 'Perfetto', 422 'stackwalk', 1, 423 -- Taken from a generated profile 424 -- debug?: boolean 425 'version', 29, 426 -- Taken from a generated profile 427 'preprocessedProfileVersion', 48, 428 -- abi?: string 429 -- misc?: string 430 -- oscpu?: string 431 -- mainMemory?: Bytes 432 -- platform?: 'Android' | 'Windows' | 'Macintosh' | 'X11' | string 433 -- toolkit?: 'gtk' | 'gtk3' | 'windows' | 'cocoa' | 'android' | string 434 -- appBuildID?: string 435 -- arguments?: string 436 -- sourceURL?: string 437 -- physicalCPUs?: number 438 -- logicalCPUs?: number 439 -- CPUName?: string 440 -- symbolicated?: boolean 441 -- symbolicationNotSupported?: boolean 442 -- updateChannel?: 'default' | 'nightly' | 'nightly-try' | 'aurora' | 'beta' | 'release' | 'esr' | string 443 -- visualMetrics?: VisualMetrics 444 -- configuration?: ProfilerConfiguration 445 'markerSchema', json_array(), 446 'sampleUnits', json_object('time', 'ms', 'eventDelay', 'ms', 'threadCPUDelta', 'µs') 447 ); 448 449-- Dumps all trace data as a Firefox profile json string 450-- See `Profile` in 451-- https://github.com/firefox-devtools/profiler/blob/main/src/types/profile.js 452-- Also 453-- https://firefox-source-docs.mozilla.org/tools/profiler/code-overview.html 454-- 455-- You would probably want to download the generated json and then open at 456-- https://https://profiler.firefox.com 457-- You can easily do this from the UI via the following SQL 458-- `SELECT CAST(export_to_firefox_profile() AS BLOB) AS profile;` 459-- The result will have a link for you to download this json as a file. 460CREATE PERFETTO FUNCTION export_to_firefox_profile() 461-- Json profile 462RETURNS STRING AS 463SELECT 464 json_object( 465 'meta', json(_export_firefox_meta()), 466 'libs', json_array(), 467 'pages', NULL, 468 'counters', NULL, 469 'profilerOverhead', NULL, 470 'threads', json(_export_firefox_threads()), 471 'profilingLog', NULL, 472 'profileGatheringLog', NULL 473 ); 474