@@ -667,6 +667,8 @@ namespace ts {
667667 createSHA256Hash ?( data : string ) : string ;
668668 getMemoryUsage ?( ) : number ;
669669 exit ( exitCode ?: number ) : void ;
670+ /*@internal */ enableCPUProfiler ?( path : string , continuation : ( ) => void ) : boolean ;
671+ /*@internal */ disableCPUProfiler ?( continuation : ( ) => void ) : boolean ;
670672 realpath ?( path : string ) : string ;
671673 /*@internal */ getEnvironmentVariable ( name : string ) : string ;
672674 /*@internal */ tryEnableSourceMapsForHost ?( ) : void ;
@@ -694,6 +696,7 @@ namespace ts {
694696 declare const process : any ;
695697 declare const global : any ;
696698 declare const __filename : string ;
699+ declare const __dirname : string ;
697700
698701 export function getNodeMajorVersion ( ) : number | undefined {
699702 if ( typeof process === "undefined" ) {
@@ -744,8 +747,9 @@ namespace ts {
744747 const byteOrderMarkIndicator = "\uFEFF" ;
745748
746749 function getNodeSystem ( ) : System {
747- const _fs = require ( "fs" ) ;
748- const _path = require ( "path" ) ;
750+ const nativePattern = / ^ n a t i v e | ^ \( [ ^ ) ] + \) $ | ^ ( i n t e r n a l [ \\ / ] | [ a - z A - Z 0 - 9 _ \s ] + ( \. j s ) ? $ ) / ;
751+ const _fs : typeof import ( "fs" ) = require ( "fs" ) ;
752+ const _path : typeof import ( "path" ) = require ( "path" ) ;
749753 const _os = require ( "os" ) ;
750754 // crypto can be absent on reduced node installations
751755 let _crypto : typeof import ( "crypto" ) | undefined ;
@@ -755,6 +759,8 @@ namespace ts {
755759 catch {
756760 _crypto = undefined ;
757761 }
762+ let activeSession : import ( "inspector" ) . Session | "stopping" | undefined ;
763+ let profilePath = "./profile.cpuprofile" ;
758764
759765 const Buffer : {
760766 new ( input : string , encoding ?: string ) : any ;
@@ -843,8 +849,10 @@ namespace ts {
843849 return 0 ;
844850 } ,
845851 exit ( exitCode ?: number ) : void {
846- process . exit ( exitCode ) ;
852+ disableCPUProfiler ( ( ) => process . exit ( exitCode ) ) ;
847853 } ,
854+ enableCPUProfiler,
855+ disableCPUProfiler,
848856 realpath,
849857 debugMode : some ( < string [ ] > process . execArgv , arg => / ^ - - ( i n s p e c t | d e b u g ) ( - b r k ) ? ( = \d + ) ? $ / i. test ( arg ) ) ,
850858 tryEnableSourceMapsForHost ( ) {
@@ -871,6 +879,92 @@ namespace ts {
871879 } ;
872880 return nodeSystem ;
873881
882+ /**
883+ * Uses the builtin inspector APIs to capture a CPU profile
884+ * See https://nodejs.org/api/inspector.html#inspector_example_usage for details
885+ */
886+ function enableCPUProfiler ( path : string , cb : ( ) => void ) {
887+ if ( activeSession ) {
888+ cb ( ) ;
889+ return false ;
890+ }
891+ const inspector : typeof import ( "inspector" ) = require ( "inspector" ) ;
892+ if ( ! inspector || ! inspector . Session ) {
893+ cb ( ) ;
894+ return false ;
895+ }
896+ const session = new inspector . Session ( ) ;
897+ session . connect ( ) ;
898+
899+ session . post ( "Profiler.enable" , ( ) => {
900+ session . post ( "Profiler.start" , ( ) => {
901+ activeSession = session ;
902+ profilePath = path ;
903+ cb ( ) ;
904+ } ) ;
905+ } ) ;
906+ return true ;
907+ }
908+
909+ /**
910+ * Strips non-TS paths from the profile, so users with private projects shouldn't
911+ * need to worry about leaking paths by submitting a cpu profile to us
912+ */
913+ function cleanupPaths ( profile : import ( "inspector" ) . Profiler . Profile ) {
914+ let externalFileCounter = 0 ;
915+ const remappedPaths = createMap < string > ( ) ;
916+ const normalizedDir = normalizeSlashes ( __dirname ) ;
917+ // Windows rooted dir names need an extra `/` prepended to be valid file:/// urls
918+ const fileUrlRoot = `file://${ getRootLength ( normalizedDir ) === 1 ? "" : "/" } ${ normalizedDir } ` ;
919+ for ( const node of profile . nodes ) {
920+ if ( node . callFrame . url ) {
921+ const url = normalizeSlashes ( node . callFrame . url ) ;
922+ if ( containsPath ( fileUrlRoot , url , useCaseSensitiveFileNames ) ) {
923+ node . callFrame . url = getRelativePathToDirectoryOrUrl ( fileUrlRoot , url , fileUrlRoot , createGetCanonicalFileName ( useCaseSensitiveFileNames ) , /*isAbsolutePathAnUrl*/ true ) ;
924+ }
925+ else if ( ! nativePattern . test ( url ) ) {
926+ node . callFrame . url = ( remappedPaths . has ( url ) ? remappedPaths : remappedPaths . set ( url , `external${ externalFileCounter } .js` ) ) . get ( url ) ! ;
927+ externalFileCounter ++ ;
928+ }
929+ }
930+ }
931+ return profile ;
932+ }
933+
934+ function disableCPUProfiler ( cb : ( ) => void ) {
935+ if ( activeSession && activeSession !== "stopping" ) {
936+ const s = activeSession ;
937+ activeSession . post ( "Profiler.stop" , ( err , { profile } ) => {
938+ if ( ! err ) {
939+ try {
940+ if ( _fs . statSync ( profilePath ) . isDirectory ( ) ) {
941+ profilePath = _path . join ( profilePath , `${ ( new Date ( ) ) . toISOString ( ) . replace ( / : / g, "-" ) } +P${ process . pid } .cpuprofile` ) ;
942+ }
943+ }
944+ catch {
945+ // do nothing and ignore fallible fs operation
946+ }
947+ try {
948+ _fs . mkdirSync ( _path . dirname ( profilePath ) , { recursive : true } ) ;
949+ }
950+ catch {
951+ // do nothing and ignore fallible fs operation
952+ }
953+ _fs . writeFileSync ( profilePath , JSON . stringify ( cleanupPaths ( profile ) ) ) ;
954+ }
955+ activeSession = undefined ;
956+ s . disconnect ( ) ;
957+ cb ( ) ;
958+ } ) ;
959+ activeSession = "stopping" ;
960+ return true ;
961+ }
962+ else {
963+ cb ( ) ;
964+ return false ;
965+ }
966+ }
967+
874968 function bufferFrom ( input : string , encoding ?: string ) : Buffer {
875969 // See https://github.com/Microsoft/TypeScript/issues/25652
876970 return Buffer . from && ( Buffer . from as Function ) !== Int8Array . from
0 commit comments