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