diff --git a/handlers/Main.cfc b/handlers/Main.cfc index 6640f3c..8b9081d 100644 --- a/handlers/Main.cfc +++ b/handlers/Main.cfc @@ -210,6 +210,62 @@ component extends="coldbox.system.RestHandler" { ); } + /** + * Render a specific Lucee SQL view (grouped/timeline/slowest) for lazy loading + */ + function renderLuceeSqlView( event, rc, prc ){ + event.paramValue( "viewType", "timeline" ); + var profiler = variables.debuggerService.getProfilerById( rc.id ); + + if ( profiler.isEmpty() || !profiler.keyExists( "cfQueries" ) ) { + return "
Profiler not found or no SQL data
"; + } + + var formatter = variables.debuggerService.getFormatter(); + var appPath = getSetting( "ApplicationPath" ); + + if ( rc.viewType == "grouped" ) { + return view( + view : "main/panels/requestTracker/luceeSqlGrouped", + module : "cbdebugger", + args : { + profiler : profiler, + debuggerService : variables.debuggerService, + debuggerConfig : variables.debuggerConfig, + formatter : formatter, + appPath : appPath + }, + prePostExempt : true + ); + } else if ( rc.viewType == "slowest" ) { + return view( + view : "main/panels/requestTracker/luceeSqlTable", + module : "cbdebugger", + args : { + sqlData : duplicate( profiler.cfQueries.all ).sort( function( a, b ){ + return a.executionTime < b.executionTime ? 1 : -1; + } ), + debuggerService : variables.debuggerService, + formatter : formatter, + appPath : appPath + }, + prePostExempt : true + ); + } else { + return view( + view : "main/panels/requestTracker/luceeSqlTable", + module : "cbdebugger", + args : { + sqlData : profiler.cfQueries.all, + debuggerService : variables.debuggerService, + formatter : formatter, + appPath : appPath + }, + prePostExempt : true + ); + } + } + /** * Export a profiler report as json */ diff --git a/models/DebuggerService.cfc b/models/DebuggerService.cfc index a5f6097..504ccda 100755 --- a/models/DebuggerService.cfc +++ b/models/DebuggerService.cfc @@ -80,6 +80,8 @@ component variables.debugPassword = variables.debuggerConfig.debugPassword; // uuid helper variables.uuid = createObject( "java", "java.util.UUID" ); + // Cache source lookups to avoid reading files for every SQL row. + variables.sourceFunctionCache = {}; // Store Environment struct variables.environment = { @@ -645,6 +647,65 @@ component return getExceptionBean().openInEditorURL( argumentCollection = arguments ); } + /** + * Resolve the function name that contains a source location like path.cfc:123. + */ + string function getFunctionNameForSource( required string source ){ + if ( !len( arguments.source ) ) { + return ""; + } + + if ( variables.sourceFunctionCache.keyExists( arguments.source ) ) { + return variables.sourceFunctionCache[ arguments.source ]; + } + + var result = ""; + + try { + var lineMatch = arguments.source.reFind( ":([0-9]+)$", 1, true ); + + if ( !lineMatch.len.len() || lineMatch.len[ 2 ] == 0 ) { + variables.sourceFunctionCache[ arguments.source ] = result; + return result; + } + + var lineNumber = arguments.source.mid( lineMatch.pos[ 2 ], lineMatch.len[ 2 ] ); + var template = arguments.source.left( lineMatch.pos[ 1 ] - 1 ); + + if ( !fileExists( template ) ) { + var normalizedTemplate = template.replace( "\root\", "\", "one" ).replace( "/root/", "/", "one" ); + + if ( fileExists( normalizedTemplate ) ) { + template = normalizedTemplate; + } + } + + if ( !fileExists( template ) || !isNumeric( lineNumber ) ) { + variables.sourceFunctionCache[ arguments.source ] = result; + return result; + } + + var lines = fileRead( template ).listToArray( chr( 10 ), true ); + var startLine = min( val( lineNumber ), lines.len() ); + var functionRE = "(^|\s)(private|public|remote|package)?\s*([\w\.\[\]]+\s+)?function\s+([A-Za-z_$][\w$]*)\s*\("; + + for ( var i = startLine; i >= 1; i-- ) { + var declaration = lines[ i ].replace( chr( 13 ), "", "all" ).trim(); + var match = declaration.reFindNoCase( functionRE, 1, true ); + + if ( match.len.len() >= 5 && match.len[ 5 ] > 0 ) { + result = declaration.mid( match.pos[ 5 ], match.len[ 5 ] ); + break; + } + } + } catch ( any e ) { + result = ""; + } + + variables.sourceFunctionCache[ arguments.source ] = result; + return result; + } + /** * Get the exception bean helper lazy loaded * diff --git a/models/Formatter.cfc b/models/Formatter.cfc index edc9924..7b5055f 100644 --- a/models/Formatter.cfc +++ b/models/Formatter.cfc @@ -45,11 +45,7 @@ component singleton { .listToArray( variables.NEW_LINE ) .map( ( item ) => item.trim() ) // comma spacing - .map( ( item ) => item.reReplace( - "\s*(?![^()]*\))(,)\s*", - ",#variables.NEW_LINE##indent#", - "all" - ) ) + .map( ( item ) => breakOnCommasOutsideParentheses( item, indent ) ) // Parenthesis spacing .map( ( item ) => item.reReplace( "\((\w)", "( \1", "all" ) ) .map( ( item ) => item.reReplace( "(\w)\)", "\1 )", "all" ) ) @@ -72,6 +68,39 @@ component singleton { .toList( variables.NEW_LINE ); } + /** + * Regex lookaheads are too expensive for large SQL strings on Lucee 7. + */ + private string function breakOnCommasOutsideParentheses( required string target, required string indent ){ + var result = []; + var depth = 0; + var length = arguments.target.len(); + + for ( var i = 1; i <= length; i++ ) { + var char = arguments.target.mid( i, 1 ); + + if ( char == "(" ) { + depth++; + } else if ( char == ")" && depth > 0 ) { + depth--; + } + + if ( char == "," && depth == 0 ) { + result.append( ",#variables.NEW_LINE##arguments.indent#" ); + + while ( i < length && arguments.target.mid( i + 1, 1 ).reFind( "\s" ) ) { + i++; + } + + continue; + } + + result.append( char ); + } + + return result.toList( "" ); + } + /** * Format an incoming json string to a pretty version * diff --git a/views/main/panels/requestTracker/luceeSqlGrouped.cfm b/views/main/panels/requestTracker/luceeSqlGrouped.cfm new file mode 100644 index 0000000..c48dd88 --- /dev/null +++ b/views/main/panels/requestTracker/luceeSqlGrouped.cfm @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + + + + + + + +
CountQuery
+
+ #args.profiler.cfQueries.grouped[ sqlHash ].count# +
+
+ + + + + +
#withoutDumbWhitespace#
+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
TimestampExecution TimeDatasourceSource/Params
+ #timeFormat( + args.debuggerService.fromEpoch( q.startTime ), + "hh:MM:SS.l tt" + )# + + #numberFormat( q.executionTime / 1000000 )# ms + + #( q.datasource ?: "QoQ" )# + + +
+ + Called From: + + + + + +
+ #displaySource# +
+ +
+ Function: + #functionName# + +
+
+
+
+
+
+
diff --git a/views/main/panels/requestTracker/luceeSqlPanel.cfm b/views/main/panels/requestTracker/luceeSqlPanel.cfm index 72adea5..f67895a 100755 --- a/views/main/panels/requestTracker/luceeSqlPanel.cfm +++ b/views/main/panels/requestTracker/luceeSqlPanel.cfm @@ -2,26 +2,47 @@ - formatter = args.debuggerService.getFormatter(); - appPath = getSetting( "ApplicationPath" ); totalExecutionTime = numberFormat( args.profiler.cfQueries.totalExecutionTime / 1000000 ); -
@@ -82,8 +103,8 @@
+ + + + Loading... +
@@ -121,160 +147,40 @@
- +
No queries executed
- +
- - - - - - - - - - - - - - - - - - - -
CountQuery
-
- #args.profiler.cfQueries.grouped[ sqlHash ].count# -
-
- - - - - -
#withoutDumbWhitespace#
-
-
- - - - - - - - - - - - - - - - - - - - -
TimestampExecution TimeDatasourceSource/Params
- #timeFormat( - args.debuggerService.fromEpoch( q.startTime ), - "hh:MM:SS.l tt" - )# - - #numberFormat( q.executionTime / 1000000 )# ms - - #( q.datasource ?: "QoQ" )# - - -
- - Called From: - - - - - -
- - #q.src# - -
-
-
-
-
+ Select a view above to load SQL queries
- +
- #view( - view : "main/panels/requestTracker/luceeSqlTable", - module : "cbdebugger", - args : { - sqlData : args.profiler.cfQueries.all, - debuggerService : args.debuggerService, - formatter : formatter, - appPath : appPath - }, - prePostExempt : true - )# -
+ x-ref="sqlView-grouped" + > - +
- #view( - view : "main/panels/requestTracker/luceeSqlTable", - module : "cbdebugger", - args : { - sqlData : args.profiler.cfQueries.all.sort( ( a, b ) => a.executionTime < b.executionTime ? 1 : -1 ), - debuggerService : args.debuggerService, - formatter : formatter, - appPath : appPath - }, - prePostExempt : true - )# -
+ x-ref="sqlView-timeline" + > + + +
diff --git a/views/main/panels/requestTracker/luceeSqlTable.cfm b/views/main/panels/requestTracker/luceeSqlTable.cfm index 679a9c2..805d5be 100644 --- a/views/main/panels/requestTracker/luceeSqlTable.cfm +++ b/views/main/panels/requestTracker/luceeSqlTable.cfm @@ -9,7 +9,8 @@ Timestamp - Execution Time + Time + Records Datasource Query @@ -17,6 +18,9 @@ + + + #timeFormat( @@ -29,24 +33,28 @@ #numberFormat( ( q.executionTime / 1000000 ) )# ms + + #q.keyExists( "recordCount" ) ? numberFormat( q.recordCount ) : "-"# + + #( q.datasource ?: "QoQ" )# -