From af3432cfde4c3218a716cf4153a8d99c392206cf Mon Sep 17 00:00:00 2001 From: Bartosz Taudul Date: Sat, 12 Sep 2026 21:56:19 +0200 Subject: [PATCH] Fetch the startup trace instead of preloading the embedded one. The preloaded data package made every ?t= load download embed.tracy as well. The startup glue now fetches embed.tracy only when no URL is given, into the same /url.tracy slot, and the boot code has a single open path. --- .github/workflows/emscripten.yml | 2 +- profiler/CMakeLists.txt | 1 - profiler/src/main.cpp | 16 +--- profiler/wasm/url_trace_pre.js | 145 ++++++++++++++++--------------- 4 files changed, 77 insertions(+), 87 deletions(-) diff --git a/.github/workflows/emscripten.yml b/.github/workflows/emscripten.yml index 71721f91..bdaedb84 100644 --- a/.github/workflows/emscripten.yml +++ b/.github/workflows/emscripten.yml @@ -57,7 +57,7 @@ jobs: mkdir -p bin cp profiler/build/index.html bin cp profiler/build/favicon.svg bin - cp profiler/build/tracy-profiler.data bin + cp profiler/build/embed.tracy bin cp profiler/build/tracy-profiler.js.gz bin cp profiler/build/tracy-profiler.js.zst bin cp profiler/build/tracy-profiler.wasm.gz bin diff --git a/profiler/CMakeLists.txt b/profiler/CMakeLists.txt index 1baa3ce4..1abcb133 100644 --- a/profiler/CMakeLists.txt +++ b/profiler/CMakeLists.txt @@ -372,7 +372,6 @@ if(EMSCRIPTEN) -sENVIRONMENT=web,worker -sDISABLE_EXCEPTION_CATCHING=0 "--pre-js;${CMAKE_CURRENT_LIST_DIR}/wasm/url_trace_pre.js" - --preload-file embed.tracy ) file(DOWNLOAD https://share.nereid.pl/i/embed.tracy ${CMAKE_CURRENT_BINARY_DIR}/embed.tracy EXPECTED_MD5 f018919fcd34de2fb349be8be354048b) diff --git a/profiler/src/main.cpp b/profiler/src/main.cpp index 547f7328..565c1e22 100644 --- a/profiler/src/main.cpp +++ b/profiler/src/main.cpp @@ -246,25 +246,17 @@ int main( int argc, char** argv ) if( f ) fclose( f ); return f != nullptr; }; - const bool hasUrl = FileExists( "/url.tracy" ) || FileExists( "/url.tracy.failed" ); - if( hasUrl ) + if( FileExists( "/url.tracy" ) ) { try { - if( FileExists( "/url.tracy" ) ) - { - initFileOpen.reset( tracy::FileRead::Open( "/url.tracy" ) ); - } + initFileOpen.reset( tracy::FileRead::Open( "/url.tracy" ) ); } catch( const tracy::NotTracyDump& ) { EM_ASM( alert( "The provided URL did not contain a valid Tracy trace." ) ); } catch( const tracy::FileReadError& ) { EM_ASM( alert( "The trace from the provided URL could not be read." ) ); } catch( const tracy::UnsupportedVersion& ) { EM_ASM( alert( "The trace from the provided URL requires a newer version of Tracy." ) ); } catch( const tracy::LegacyVersion& ) { EM_ASM( alert( "The trace from the provided URL is in a legacy format." ) ); } } - else - { - initFileOpen = std::unique_ptr( tracy::FileRead::Open( "embed.tracy" ) ); - } #endif if( argc == 2 ) { @@ -397,10 +389,6 @@ int main( int argc, char** argv ) { view.store( std::make_shared( RunOnMainThread, *initFileOpen, SetWindowTitleCallback, SetupScaleCallback, AttentionCallback, s_achievements ), std::memory_order_release ); } - catch( const tracy::UnsupportedVersion& ) - { - EM_ASM( alert( "The trace from the provided URL requires a newer version of Tracy." ) ); - } catch( const std::exception& e ) { const std::string msg = std::string( "Failed to load the trace: " ) + e.what(); diff --git a/profiler/wasm/url_trace_pre.js b/profiler/wasm/url_trace_pre.js index 189bb69d..cb95e77d 100644 --- a/profiler/wasm/url_trace_pre.js +++ b/profiler/wasm/url_trace_pre.js @@ -1,91 +1,94 @@ -// URL-mode trace loader: opens ?t= as the startup trace instead of embed.tracy. +// Startup trace loader: fetches ?t=, or embed.tracy next to the page when no +// URL is given, into the /url.tracy virtual file. if( typeof document !== 'undefined' ) { - const tracyTraceUrl = new URLSearchParams( window.location.search ).get( 't' ); - if( tracyTraceUrl ) + const tracyTraceParam = new URLSearchParams( window.location.search ).get( 't' ); + let tracyUrl = null; + let tracyUrlError = null; + if( tracyTraceParam ) { - let tracyUrl = null; - let tracyUrlError = null; - try { tracyUrl = new URL( tracyTraceUrl, window.location.href ); } + try { tracyUrl = new URL( tracyTraceParam, window.location.href ); } catch( e ) { tracyUrlError = 'invalid URL'; } if( tracyUrl && ( ( tracyUrl.protocol !== 'https:' && tracyUrl.protocol !== 'http:' ) || tracyUrl.username !== '' || tracyUrl.password !== '' ) ) tracyUrlError = 'unsupported URL (https/http without embedded credentials required)'; + } + else + { + tracyUrl = new URL( 'embed.tracy', window.location.href ); + } - // Emscripten builds compile without exception catching, so FileRead's - // header validation aborts the page instead of unwinding; gate in JS. - const isTracyFile = ( b ) => b.length >= 4 && ( - ( b[0] === 0x74 && b[1] === 0x72 && b[2] === 0xfd && b[3] === 0x50 ) || - ( b[0] === 0x74 && b[1] === 0x6c && b[2] === 0x5a && b[3] === 0x04 ) || - ( b[0] === 0x74 && b[1] === 0x5a && b[2] === 0x73 && b[3] === 0x74 ) ); + const isTracyFile = ( b ) => b.length >= 4 && ( + ( b[0] === 0x74 && b[1] === 0x72 && b[2] === 0xfd && b[3] === 0x50 ) || + ( b[0] === 0x74 && b[1] === 0x6c && b[2] === 0x5a && b[3] === 0x04 ) || + ( b[0] === 0x74 && b[1] === 0x5a && b[2] === 0x73 && b[3] === 0x74 ) ); - const tracyTraceData = ( () => + const tracyTraceData = ( () => + { + if( tracyUrlError ) return Promise.reject( tracyUrlError ); + const opts = { mode: 'cors' }; + if( tracyTraceParam && tracyUrl.protocol === 'http:' ) { - if( tracyUrlError ) return Promise.reject( tracyUrlError ); - const opts = { mode: 'cors' }; - if( tracyUrl.protocol === 'http:' ) + const loopback = tracyUrl.hostname === 'localhost' || tracyUrl.hostname.endsWith( '.localhost' ) || + tracyUrl.hostname === '[::1]' || /^127\./.test( tracyUrl.hostname ); + // Local Network Access: pre-resolve classification, relaxes mixed content for local targets. + opts.targetAddressSpace = loopback ? 'loopback' : 'local'; + } + const ctrl = new AbortController(); + opts.signal = ctrl.signal; + const timeout = setTimeout( () => ctrl.abort(), 300000 ); + Module.setStatus( 'Loading trace...' ); + return fetch( tracyUrl.href, opts ).then( ( resp ) => + { + if( !resp.ok ) { clearTimeout( timeout ); throw 'HTTP status ' + resp.status; } + const total = Number( resp.headers.get( 'Content-Length' ) || 0 ); + if( !resp.body || !total ) { - const loopback = tracyUrl.hostname === 'localhost' || tracyUrl.hostname.endsWith( '.localhost' ) || - tracyUrl.hostname === '[::1]' || /^127\./.test( tracyUrl.hostname ); - // Local Network Access: pre-resolve classification, relaxes mixed content for local targets. - opts.targetAddressSpace = loopback ? 'loopback' : 'local'; + return resp.arrayBuffer().then( + ( ab ) => { clearTimeout( timeout ); return new Uint8Array( ab ); }, + ( e ) => { clearTimeout( timeout ); throw e; } ); } - const ctrl = new AbortController(); - opts.signal = ctrl.signal; - const timeout = setTimeout( () => ctrl.abort(), 300000 ); - Module.setStatus( 'Loading trace...' ); - return fetch( tracyUrl.href, opts ).then( ( resp ) => + const reader = resp.body.getReader(); + const chunks = []; + let received = 0; + const pump = () => reader.read().then( ( { done, value } ) => { - if( !resp.ok ) { clearTimeout( timeout ); throw 'HTTP status ' + resp.status; } - const total = Number( resp.headers.get( 'Content-Length' ) || 0 ); - if( !resp.body || !total ) + if( done ) { - return resp.arrayBuffer().then( - ( ab ) => { clearTimeout( timeout ); return new Uint8Array( ab ); }, - ( e ) => { clearTimeout( timeout ); throw e; } ); + clearTimeout( timeout ); + const out = new Uint8Array( received ); + let off = 0; + for( const c of chunks ) { out.set( c, off ); off += c.length; } + return out; } - const reader = resp.body.getReader(); - const chunks = []; - let received = 0; - const pump = () => reader.read().then( ( { done, value } ) => - { - if( done ) - { - clearTimeout( timeout ); - const out = new Uint8Array( received ); - let off = 0; - for( const c of chunks ) { out.set( c, off ); off += c.length; } - return out; - } - chunks.push( value ); - received += value.length; - Module.setStatus( 'Loading trace (' + received + '/' + total + ')' ); - return pump(); - } ); + chunks.push( value ); + received += value.length; + Module.setStatus( 'Loading trace (' + received + '/' + total + ')' ); return pump(); - }, - ( e ) => { clearTimeout( timeout ); throw ( e && e.name === 'AbortError' ) ? 'download timed out' : 'fetch failed'; } ); - } )(); + } ); + return pump(); + }, + ( e ) => { clearTimeout( timeout ); throw ( e && e.name === 'AbortError' ) ? 'download timed out' : 'fetch failed'; } ); + } )(); - Module.preRun = Module.preRun || []; - Module.preRun.push( () => - { - addRunDependency( 'tracy-url-trace' ); - tracyTraceData.then( - ( bytes ) => - { - if( !isTracyFile( bytes ) ) - { - FS.writeFile( '/url.tracy.failed', new Uint8Array( 0 ) ); - alert( 'Cannot load trace from ' + tracyTraceUrl + ': not a Tracy trace file' ); - } - else FS.writeFile( '/url.tracy', bytes ); - }, - ( err ) => + Module.preRun = Module.preRun || []; + Module.preRun.push( () => + { + addRunDependency( 'tracy-startup-trace' ); + tracyTraceData.then( + ( bytes ) => + { + if( !isTracyFile( bytes ) ) { FS.writeFile( '/url.tracy.failed', new Uint8Array( 0 ) ); - alert( 'Cannot load trace from ' + tracyTraceUrl + ': ' + err ); - } ).finally( () => removeRunDependency( 'tracy-url-trace' ) ); - } ); - } + alert( 'Cannot load trace from ' + tracyUrl.href + ': not a Tracy trace file' ); + } + else FS.writeFile( '/url.tracy', bytes ); + }, + ( err ) => + { + FS.writeFile( '/url.tracy.failed', new Uint8Array( 0 ) ); + alert( 'Cannot load trace from ' + tracyUrl.href + ': ' + err ); + } ).finally( () => removeRunDependency( 'tracy-startup-trace' ) ); + } ); }