diff --git a/builds/posix/Makefile.in b/builds/posix/Makefile.in index 9acd4c4e17..5c10d94ee3 100644 --- a/builds/posix/Makefile.in +++ b/builds/posix/Makefile.in @@ -543,7 +543,7 @@ $(NBACKUP): $(NBACKUP_Objects) $(COMMON_LIB) # plugins - some of them are required to build examples, use separate entry for them # -.PHONY: udr legacy_user_management legacy_auth_server trace auth_debug udf_compat chacha +.PHONY: udr legacy_user_management legacy_auth_server trace auth_debug udf_compat chacha profiler UDR_PLUGIN = $(call makePluginName,udr_engine) LEGACY_USER_MANAGER = $(call makePluginName,Legacy_UserManager) LEGACY_AUTH_SERVER = $(call makePluginName,Legacy_Auth) @@ -553,13 +553,14 @@ AUTH_DEBUGGER = $(call makePluginName,Auth_Debug) UDF_BACKWARD_COMPATIBILITY_BASENAME = $(LIB_PREFIX)udf_compat.$(SHRLIB_EXT) UDF_BACKWARD_COMPATIBILITY = $(PLUGINS)/udr/$(UDF_BACKWARD_COMPATIBILITY_BASENAME) CHACHA = $(call makePluginName,ChaCha) +PROFILER = $(call makePluginName,Default_Profiler) BUILD_DEBUG:= ifeq ($(TARGET),Debug) BUILD_DEBUG:=auth_debug endif -plugins: udr legacy_user_management legacy_auth_server srp_user_management trace $(BUILD_DEBUG) udf_compat chacha +plugins: udr legacy_user_management legacy_auth_server srp_user_management trace $(BUILD_DEBUG) udf_compat chacha profiler udr: $(UDR_PLUGIN) $(PLUGINS)/udr_engine.conf @@ -588,6 +589,12 @@ $(LEGACY_AUTH_SERVER): $(LEGACY_AUTH_SERVER_Objects) $(COMMON_LIB) $(LINK_PLUGIN) $(call LIB_LINK_SONAME,$(notdir $@).0) -o $@ $^ $(LINK_PLUG_LIBS) $(FIREBIRD_LIBRARY_LINK)\ $(call LIB_LINK_DARWIN_INSTALL_NAME,plugins/libLegacy_Auth.$(SHRLIB_EXT)) +profiler: $(PROFILER) + +$(PROFILER): $(Profiler_Objects) $(COMMON_LIB) + $(LINK_PLUGIN) $(call LIB_LINK_SONAME,$(notdir $@).0) -o $@ $^ $(LINK_PLUG_LIBS) $(FIREBIRD_LIBRARY_LINK)\ + $(call LIB_LINK_DARWIN_INSTALL_NAME,plugins/libDefault_Profiler.$(SHRLIB_EXT)) + trace: $(FBTRACE) $(FBTRACE): $(FBTRACE_UTIL_Objects) $(COMMON_LIB) diff --git a/builds/posix/make.shared.variables b/builds/posix/make.shared.variables index fe286cda7e..3d48c7fc58 100644 --- a/builds/posix/make.shared.variables +++ b/builds/posix/make.shared.variables @@ -72,9 +72,12 @@ AllObjects += $(Remote_Common) $(Remote_Server) $(Remote_Client) # Chacha plugin Chacha_Objects:= $(call dirObjects,plugins/crypt/chacha) - AllObjects += $(Chacha_Objects) +# Profiler plugin +Profiler_Objects:= $(call dirObjects,plugins/profiler) +AllObjects += $(Profiler_Objects) + # Engine Engine_Objects:= $(call dirObjects,jrd) $(call dirObjects,dsql) $(call dirObjects,jrd/extds) \ $(call dirObjects,jrd/recsrc) $(call dirObjects,jrd/replication) $(call dirObjects,jrd/trace) \ diff --git a/builds/win32/msvc15/Firebird.sln b/builds/win32/msvc15/Firebird.sln index 910519109e..c81bf38974 100644 --- a/builds/win32/msvc15/Firebird.sln +++ b/builds/win32/msvc15/Firebird.sln @@ -82,6 +82,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "udf_compat", "udf_compat.vc EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "chacha", "chacha.vcxproj", "{F2E1A852-5A4B-4162-9DA8-0363805FCFD0}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "default_profiler", "default_profiler.vcxproj", "{9821F2C0-4EC1-4ACB-BF32-DEB4C21032DE}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Win32 = Debug|Win32 @@ -234,14 +236,6 @@ Global {DEE75AD5-F165-40E1-80B2-400E27725D5C}.Release|Win32.Build.0 = Release|Win32 {DEE75AD5-F165-40E1-80B2-400E27725D5C}.Release|x64.ActiveCfg = Release|x64 {DEE75AD5-F165-40E1-80B2-400E27725D5C}.Release|x64.Build.0 = Release|x64 - {EBB8361B-49D5-43A5-8771-940DF3E308EF}.Debug|Win32.ActiveCfg = Debug|Win32 - {EBB8361B-49D5-43A5-8771-940DF3E308EF}.Debug|Win32.Build.0 = Debug|Win32 - {EBB8361B-49D5-43A5-8771-940DF3E308EF}.Debug|x64.ActiveCfg = Debug|x64 - {EBB8361B-49D5-43A5-8771-940DF3E308EF}.Debug|x64.Build.0 = Debug|x64 - {EBB8361B-49D5-43A5-8771-940DF3E308EF}.Release|Win32.ActiveCfg = Release|Win32 - {EBB8361B-49D5-43A5-8771-940DF3E308EF}.Release|Win32.Build.0 = Release|Win32 - {EBB8361B-49D5-43A5-8771-940DF3E308EF}.Release|x64.ActiveCfg = Release|x64 - {EBB8361B-49D5-43A5-8771-940DF3E308EF}.Release|x64.Build.0 = Release|x64 {4BCC693D-1745-45ED-8302-E5E2F979549A}.Debug|Win32.ActiveCfg = Debug|Win32 {4BCC693D-1745-45ED-8302-E5E2F979549A}.Debug|Win32.Build.0 = Debug|Win32 {4BCC693D-1745-45ED-8302-E5E2F979549A}.Debug|x64.ActiveCfg = Debug|x64 @@ -360,6 +354,14 @@ Global {F2E1A852-5A4B-4162-9DA8-0363805FCFD0}.Release|Win32.Build.0 = Release|Win32 {F2E1A852-5A4B-4162-9DA8-0363805FCFD0}.Release|x64.ActiveCfg = Release|x64 {F2E1A852-5A4B-4162-9DA8-0363805FCFD0}.Release|x64.Build.0 = Release|x64 + {9821F2C0-4EC1-4ACB-BF32-DEB4C21032DE}.Debug|Win32.ActiveCfg = Debug|Win32 + {9821F2C0-4EC1-4ACB-BF32-DEB4C21032DE}.Debug|Win32.Build.0 = Debug|Win32 + {9821F2C0-4EC1-4ACB-BF32-DEB4C21032DE}.Debug|x64.ActiveCfg = Debug|x64 + {9821F2C0-4EC1-4ACB-BF32-DEB4C21032DE}.Debug|x64.Build.0 = Debug|x64 + {9821F2C0-4EC1-4ACB-BF32-DEB4C21032DE}.Release|Win32.ActiveCfg = Release|Win32 + {9821F2C0-4EC1-4ACB-BF32-DEB4C21032DE}.Release|Win32.Build.0 = Release|Win32 + {9821F2C0-4EC1-4ACB-BF32-DEB4C21032DE}.Release|x64.ActiveCfg = Release|x64 + {9821F2C0-4EC1-4ACB-BF32-DEB4C21032DE}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/builds/win32/msvc15/default_profiler.filters b/builds/win32/msvc15/default_profiler.filters new file mode 100644 index 0000000000..546fbe0374 --- /dev/null +++ b/builds/win32/msvc15/default_profiler.filters @@ -0,0 +1,29 @@ + + + + + {3ce83b57-8830-4673-8c73-0b1e607fe2e7} + cpp;c;cxx;rc;def;r;odl;idl;hpj;bat + + + {5a3fd64b-6882-4e1d-b7ac-83fb12f1c8e8} + h;hpp;hxx;hm;inl + + + {e5b5aa94-df00-43cf-ac73-248edd148f20} + + + + + Source files + + + + + Resource files + + + + + + \ No newline at end of file diff --git a/builds/win32/msvc15/default_profiler.vcxproj b/builds/win32/msvc15/default_profiler.vcxproj new file mode 100644 index 0000000000..ebce3a94fa --- /dev/null +++ b/builds/win32/msvc15/default_profiler.vcxproj @@ -0,0 +1,230 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {9821F2C0-4EC1-4ACB-BF32-DEB4C21032DE} + 10.0.17763.0 + 10.0 + 10.0 + + + + DynamicLibrary + false + MultiByte + v141_xp + v142 + v143 + + + DynamicLibrary + false + MultiByte + v141_xp + v142 + v143 + + + DynamicLibrary + false + MultiByte + v141 + v142 + v143 + + + DynamicLibrary + false + MultiByte + v141 + v142 + v143 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 + true + false + false + false + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + AllRules.ruleset + + + ..\..\..\temp\$(PlatformName)\$(Configuration)\firebird\plugins\ + ..\..\..\temp\$(PlatformName)\$(Configuration)\firebird\plugins\ + ..\..\..\temp\$(PlatformName)\$(Configuration)\firebird\plugins\ + ..\..\..\temp\$(PlatformName)\$(Configuration)\firebird\plugins\ + + + + _DEBUG;%(PreprocessorDefinitions) + true + Win32 + + + Disabled + _DEBUG;_WINDOWS;_USRDLL;WINDOWS_ONLY;SUPERCLIENT;WIN32;DEV_BUILD;%(PreprocessorDefinitions) + EditAndContinue + + + comctl32.lib;ws2_32.lib;mpr.lib;version.lib;%(AdditionalDependencies) + ..\..\..\temp\$(Platform)\$(Configuration)\firebird\plugins\$(ProjectName).dll + ..\defs\plugin.def + false + + + Windows + + + + + _DEBUG;%(PreprocessorDefinitions) + true + X64 + + + Disabled + _DEBUG;_WINDOWS;_USRDLL;WINDOWS_ONLY;SUPERCLIENT;WIN32;DEV_BUILD;%(PreprocessorDefinitions) + + + comctl32.lib;ws2_32.lib;mpr.lib;version.lib;%(AdditionalDependencies) + ..\..\..\temp\$(Platform)\$(Configuration)\firebird\plugins\$(ProjectName).dll + ..\defs\plugin.def + false + + + MachineX64 + Windows + + + + + _DEBUG;%(PreprocessorDefinitions) + true + Win32 + + + MaxSpeed + OnlyExplicitInline + true + Speed + NDEBUG;_WINDOWS;_USRDLL;WINDOWS_ONLY;SUPERCLIENT;WIN32;%(PreprocessorDefinitions) + + + comctl32.lib;ws2_32.lib;mpr.lib;version.lib;%(AdditionalDependencies) + ..\..\..\temp\$(Platform)\$(Configuration)\firebird\plugins\$(ProjectName).dll + ..\defs\plugin.def + false + + + Windows + + + + + _DEBUG;%(PreprocessorDefinitions) + true + X64 + + + MaxSpeed + OnlyExplicitInline + true + Speed + NDEBUG;_WINDOWS;_USRDLL;WINDOWS_ONLY;SUPERCLIENT;WIN32;%(PreprocessorDefinitions) + + + comctl32.lib;ws2_32.lib;mpr.lib;version.lib;%(AdditionalDependencies) + ..\..\..\temp\$(Platform)\$(Configuration)\firebird\plugins\$(ProjectName).dll + ..\defs\plugin.def + false + + + MachineX64 + Windows + + + + + + + + ..\..\..\src\jrd + ..\..\..\src\jrd + ..\..\..\src\jrd + ..\..\..\src\jrd + + + + + + + + {15605f44-bffd-444f-ad4c-55dc9d704465} + false + + + {4fe03933-98cd-4879-a135-fd9430087a6b} + + + + + + \ No newline at end of file diff --git a/builds/win32/msvc15/engine.vcxproj b/builds/win32/msvc15/engine.vcxproj index 89a580cde6..18e2ceb758 100644 --- a/builds/win32/msvc15/engine.vcxproj +++ b/builds/win32/msvc15/engine.vcxproj @@ -106,6 +106,7 @@ + @@ -302,6 +303,7 @@ + diff --git a/builds/win32/msvc15/engine.vcxproj.filters b/builds/win32/msvc15/engine.vcxproj.filters index aa5308dc3e..a85fb9dbbb 100644 --- a/builds/win32/msvc15/engine.vcxproj.filters +++ b/builds/win32/msvc15/engine.vcxproj.filters @@ -327,6 +327,9 @@ JRD files + + JRD files + JRD files @@ -896,6 +899,9 @@ Header files + + Header files + Header files diff --git a/doc/sql.extensions/README.profiler.md b/doc/sql.extensions/README.profiler.md new file mode 100644 index 0000000000..787b34ae1b --- /dev/null +++ b/doc/sql.extensions/README.profiler.md @@ -0,0 +1,349 @@ +# Profiler (FB 5.0) + +The profiler allows users to measure performance cost of SQL and PSQL code. + +It's implemented with a system package in the engine passing data to a profiler plugin. + +This documentation treats the engine and plugin parts as a single thing, in the way the default profiler is going to be used. + +The `RDB$PROFILER` package allows to profile execution of PSQL code collecting statistics of how many times each line was executed along with its minimum, maximum and accumulated execution times (with nanoseconds precision), as well open and fetch statistics of implicit and explicit SQL cursors. + +To collect profile data, an user must first start a profile session with `RDB$PROFILER.START_SESSION`. This function returns an profile session ID which is later stored in the profiler snapshot tables to be queried and analyzed by the user. + +After a session is started, PSQL and SQL statements statistics starts to be collected in memory. Note that a profile session collects data only of statements executed in the same attachment where the session was started. + +Data is aggregated and stored per requests (i.e. a statement execution). When querying snapshot tables, user may do extra aggregation per statements or use the auxiliary views that do that automatically. + +A session may be paused to temporary disable statistics collecting. It may be resumed later to return statistics collection in the same session. + +A new session may be started when a session is already active. In this case it has the same semantics of finishing the current session with `RDB$PROFILER.FINISH_SESSION(FALSE)` so snapshots tables are not updated in the same moment. + +To analyze the collected data, the user must flush the data to the snapshot tables, which may be done finishing or pausing a session (with `FLUSH` parameter set to `TRUE`) or calling `RDB$PROFILER.FLUSH`. + +Following is a sample profile session and queries for data analysis. + +``` +-- Preparation - create table and routines that will be analyzed + +create table tab ( + id integer not null, + val integer not null +); + +set term !; + +create or alter function mult(p1 integer, p2 integer) returns integer +as +begin + return p1 * p2; +end! + +create or alter procedure ins +as + declare n integer = 1; +begin + while (n <= 1000) + do + begin + if (mod(n, 2) = 1) then + insert into tab values (:n, mult(:n, 2)); + n = n + 1; + end +end! + +set term ;! + +-- Start profiling + +select rdb$profiler.start_session('Default_Profiler', 'Profile Session 1') from rdb$database; + +set term !; + +execute block +as +begin + execute procedure ins; + delete from tab; +end! + +set term ;! + +execute procedure rdb$profiler.finish_session(true); + +execute procedure ins; + +select rdb$profiler.start_session('Default_Profiler', 'Profile Session 2') from rdb$database; + +select mod(id, 5), + sum(val) + from tab + where id <= 50 + group by mod(id, 5) + order by sum(val); + +execute procedure rdb$profiler.finish_session(true); + +-- Data analysis + +select * from fbprof$sessions; + +select * from fbprof$psql_stats_view; + +select * from fbprof$record_source_stats_view; + +select preq.* + from fbprof$requests preq + join fbprof$sessions pses + on pses.session_id = preq.session_id and + pses.description = 'Profile Session 1'; + +select pstat.* + from fbprof$psql_stats pstat + join fbprof$sessions pses + on pses.session_id = pstat.session_id and + pses.description = 'Profile Session 1' + order by pstat.session_id, + pstat.request_id, + pstat.line_num, + pstat.column_num; + +select pstat.* + from fbprof$record_source_stats pstat + join fbprof$sessions pses + on pses.session_id = pstat.session_id and + pses.description = 'Profile Session 2' + order by pstat.session_id, + pstat.request_id, + pstat.cursor_id, + pstat.record_source_id; +``` + +## Function `START_SESSION` + +`RDB$PROFILER.START_SESSION` starts a new profiler session, turns it the current session and return its identifier. + +Input parameters: + - `PLUGIN_NAME` type `VARCHAR(255) CHARACTER SET UTF8` + - `DESCRIPTION` type `VARCHAR(255) CHARACTER SET UTF8` + +Return type: `BIGINT NOT NULL`. + +## Procedure `PAUSE_SESSION` + +`RDB$PROFILER.PAUSE_SESSION` pauses the current profiler session so the next executed statements statistics are not collected. + +If `FLUSH` is `TRUE` the snapshot tables are updated with data up to the current moment. Otherwise data remains only in memory for later update. + +Calling `RDB$PROFILER.PAUSE_SESSION(TRUE)` has the same semantics of calling `RDB$PROFILER.PAUSE_SESSION(FALSE)` followed by `RDB$PROFILER.FLUSH`. + +Input parameters: + - `FLUSH` type `BOOLEAN NOT NULL` + +## Procedure `RESUME_SESSION` + +`RDB$PROFILER.RESUME_SESSION` resumes the current profiler session if it was paused so the next executed statements statistics are collected again. + +## Procedure `FINISH_SESSION` + +`RDB$PROFILER.FINISH_SESSION` finishes the current profiler session. + +If `FLUSH` is `TRUE` the snapshot tables are updated with data of the finished session (and old finished sessions not yet present in the snapshot). Otherwise data remains only in memory for later update. + +Calling `RDB$PROFILER.FINISH_SESSION(TRUE)` has the same semantics of calling `RDB$PROFILER.FINISH_SESSION(FALSE)` followed by `RDB$PROFILER.FLUSH`. + +Input parameters: + - `FLUSH` type `BOOLEAN NOT NULL` + +## Procedure `FLUSH` + +`RDB$PROFILER.FLUSH` updates the snapshot tables with data from the profile sessions in memory. + +After update data is stored in tables `FBPROF$SESSIONS`, `FBPROF$STATEMENTS`, `FBPROF$RECORD_SOURCES`, `FBPROF$REQUESTS`, `FBPROF$PSQL_STATS` and `FBPROF$RECORD_SOURCE_STATS` and may be read and analyzed by the user. + +It also removes finished sessions from memory. + +# Snapshot tables + +Snapshot tables (as well views and sequence) are automatically created in the first usage of the profiler. They are owned by the current user with read/write permissions for `PUBLIC`. + +When a session is deleted the related data in others profiler snapshot tables are automatically deleted too through foregin keys with `DELETE CASCADE` option. + +Below is the list of tables that stores profile data. + +## Table `FBPROF$SESSIONS` + + - `SESSION_ID` type `BIGINT` - Profile session ID + - `ATTACHMENT_ID` type `BIGINT` - Attachment ID + - `USER_NAME` type `CHAR(63) CHARACTER SET UTF8` - User name + - `DESCRIPTION` type `VARCHAR(255) CHARACTER SET UTF8` - Description passed in `RDB$PROFILER.START_SESSION` + - `START_TIMESTAMP` type `TIMESTAMP WITH TIME ZONE` - Moment the profile session was started + - `FINISH_TIMESTAMP` type `TIMESTAMP WITH TIME ZONE` - Moment the profile session was finished (NULL when not finished) + - Primary key: `SESSION_ID` + +## Table `FBPROF$STATEMENTS` + + - `SESSION_ID` type `BIGINT` - Profile session ID + - `STATEMENT_ID` type `BIGINT` - Statement ID + - `PARENT_STATEMENT_ID` type `BIGINT` - Parent statement ID - related to sub routines + - `STATEMENT_TYPE` type `VARCHAR(20) CHARACTER SET UTF8` - BLOCK, FUNCTION, PROCEDURE or TRIGGER + - `PACKAGE_NAME` type `CHAR(63) CHARACTER SET UTF8` - Package of FUNCTION or PROCEDURE + - `ROUTINE_NAME` type `CHAR(63) CHARACTER SET UTF8` - Routine name of FUNCTION, PROCEDURE or TRIGGER + - `SQL_TEXT` type `BLOB subtype TEXT CHARACTER SET UTF8` - SQL text for BLOCK + - Primary key: `SESSION_ID, STATEMENT_ID` + +## Table `FBPROF$RECORD_SOURCES` + + - `SESSION_ID` type `BIGINT` - Profile session ID + - `STATEMENT_ID` type `BIGINT` - Statement ID + - `CURSOR_ID` type `BIGINT` - Cursor ID + - `RECORD_SOURCE_ID` type `BIGINT` - Record source ID + - `PARENT_RECORD_SOURCE_ID` type `BIGINT` - Parent record source ID + - `ACCESS_PATH` type `VARCHAR(255) CHARACTER SET UTF8` - Access path for the record source + - Primary key: `SESSION_ID, STATEMENT_ID, CURSOR_ID, RECORD_SOURCE_ID` + +## Table `FBPROF$REQUESTS` + + - `SESSION_ID` type `BIGINT` - Profile session ID + - `REQUEST_ID` type `BIGINT` - Request ID + - `STATEMENT_ID` type `BIGINT` - Statement ID + - `CALLER_REQUEST_ID` type `BIGINT` - Caller request ID + - `START_TIMESTAMP` type `TIMESTAMP WITH TIME ZONE` - Moment this request was first gathered profile data + - `FINISH_TIMESTAMP` type `TIMESTAMP WITH TIME ZONE` - Moment this request was finished + - Primary key: `SESSION_ID, REQUEST_ID` + +## Table `FBPROF$PSQL_STATS` + + - `SESSION_ID` type `BIGINT` - Profile session ID + - `REQUEST_ID` type `BIGINT` - Request ID + - `LINE_NUM` type `INTEGER` - Line number of the statement + - `COLUMN_NUM` type `INTEGER` - Column number of the statement + - `STATEMENT_ID` type `BIGINT` - Statement ID + - `COUNTER` type `BIGINT` - Number of executed times of the statement + - `MIN_TIME` type `BIGINT` - Minimal time (in nanoseconds) of a statement execution + - `MAX_TIME` type `BIGINT` - Maximum time (in nanoseconds) of a statement execution + - `TOTAL_TIME` type `BIGINT` - Accumulated execution time (in nanoseconds) of the statement + - Primary key: `SESSION_ID, REQUEST_ID, LINE_NUM, COLUMN_NUM` + +## Table `FBPROF$RECORD_SOURCE_STATS` + + - `SESSION_ID` type `BIGINT` - Profile session ID + - `REQUEST_ID` type `BIGINT` - Request ID + - `CURSOR_ID` type `BIGINT` - Cursor ID + - `RECORD_SOURCE_ID` type `BIGINT` - Record source ID + - `STATEMENT_ID` type `BIGINT` - Statement ID + - `OPEN_COUNTER` type `BIGINT` - Number of open times of the record source + - `OPEN_MIN_TIME` type `BIGINT` - Minimal time (in nanoseconds) of a record source open + - `OPEN_MAX_TIME` type `BIGINT` - Maximum time (in nanoseconds) of a record source open + - `OPEN_TOTAL_TIME` type `BIGINT` - Accumulated open time (in nanoseconds) of the record source + - `FETCH_COUNTER` type `BIGINT` - Number of fetch times of the record source + - `FETCH_MIN_TIME` type `BIGINT` - Minimal time (in nanoseconds) of a record source fetch + - `FETCH_MAX_TIME` type `BIGINT` - Maximum time (in nanoseconds) of a record source fetch + - `FETCH_TOTAL_TIME` type `BIGINT` - Accumulated fetch time (in nanoseconds) of the record source + - Primary key: `SESSION_ID, REQUEST_ID, CURSOR_ID, RECORD_SOURCE_ID` + +# Auxiliary views + +These views help profile data extraction aggregated at statement level. + +They should be the preferred way to analyze the collected data. They can also be used together with the tables to get additional data not present on the views. + +After hot spots are found, one can drill down in the data at the request level through the tables. + +## View `FBPROF$PSQL_STATS_VIEW` +``` +select pstat.session_id, + pstat.statement_id, + sta.statement_type, + sta.package_name, + sta.routine_name, + sta.parent_statement_id, + sta_parent.statement_type parent_statement_type, + sta_parent.routine_name parent_routine_name, + (select sql_text + from fbprof$statements + where session_id = pstat.session_id and + statement_id = coalesce(sta.parent_statement_id, pstat.statement_id) + ) sql_text, + pstat.line_num, + pstat.column_num, + sum(pstat.counter) counter, + min(pstat.min_time) min_time, + max(pstat.max_time) max_time, + sum(pstat.total_time) total_time, + sum(pstat.total_time) / nullif(sum(pstat.counter), 0) avg_time + from fbprof$psql_stats pstat + join fbprof$statements sta + on sta.session_id = pstat.session_id and + sta.statement_id = pstat.statement_id + left join fbprof$statements sta_parent + on sta_parent.session_id = sta.session_id and + sta_parent.statement_id = sta.parent_statement_id + group by pstat.session_id, + pstat.statement_id, + sta.statement_type, + sta.package_name, + sta.routine_name, + sta.parent_statement_id, + sta_parent.statement_type, + sta_parent.routine_name, + pstat.line_num, + pstat.column_num + order by sum(pstat.total_time) desc +``` + +## View `FBPROF$RECORD_SOURCE_STATS_VIEW` +``` +select rstat.session_id, + rstat.statement_id, + sta.statement_type, + sta.package_name, + sta.routine_name, + sta.parent_statement_id, + sta_parent.statement_type parent_statement_type, + sta_parent.routine_name parent_routine_name, + (select sql_text + from fbprof$statements + where session_id = rstat.session_id and + statement_id = coalesce(sta.parent_statement_id, rstat.statement_id) + ) sql_text, + rstat.cursor_id, + rstat.record_source_id, + recsrc.parent_record_source_id, + recsrc.access_path, + sum(rstat.open_counter) open_counter, + min(rstat.open_min_time) open_min_time, + max(rstat.open_max_time) open_max_time, + sum(rstat.open_total_time) open_total_time, + sum(rstat.open_total_time) / nullif(sum(rstat.open_counter), 0) open_avg_time, + sum(rstat.fetch_counter) fetch_counter, + min(rstat.fetch_min_time) fetch_min_time, + max(rstat.fetch_max_time) fetch_max_time, + sum(rstat.fetch_total_time) fetch_total_time, + sum(rstat.fetch_total_time) / nullif(sum(rstat.fetch_counter), 0) fetch_avg_time, + coalesce(sum(rstat.open_total_time), 0) + coalesce(sum(rstat.fetch_total_time), 0) open_fetch_total_time + from fbprof$record_source_stats rstat + join fbprof$record_sources recsrc + on recsrc.session_id = rstat.session_id and + recsrc.statement_id = rstat.statement_id and + recsrc.cursor_id = rstat.cursor_id and + recsrc.record_source_id = rstat.record_source_id + join fbprof$statements sta + on sta.session_id = rstat.session_id and + sta.statement_id = rstat.statement_id + left join fbprof$statements sta_parent + on sta_parent.session_id = sta.session_id and + sta_parent.statement_id = sta.parent_statement_id + group by rstat.session_id, + rstat.statement_id, + sta.statement_type, + sta.package_name, + sta.routine_name, + sta.parent_statement_id, + sta_parent.statement_type, + sta_parent.routine_name, + rstat.cursor_id, + rstat.record_source_id, + recsrc.parent_record_source_id, + recsrc.access_path + order by coalesce(sum(rstat.open_total_time), 0) + coalesce(sum(rstat.fetch_total_time), 0) desc +``` diff --git a/src/common/TimeZoneUtil.h b/src/common/TimeZoneUtil.h index f9b20ca099..6768bc288d 100644 --- a/src/common/TimeZoneUtil.h +++ b/src/common/TimeZoneUtil.h @@ -113,6 +113,13 @@ public: static ISC_TIMESTAMP_TZ getCurrentSystemTimeStamp(); static ISC_TIMESTAMP_TZ getCurrentGmtTimeStamp(); + static ISC_TIMESTAMP_TZ getCurrentTimeStamp(USHORT timeZone) + { + auto tsTz = getCurrentGmtTimeStamp(); + tsTz.time_zone = timeZone; + return tsTz; + } + static void validateGmtTimeStamp(NoThrowTimeStamp& ts); static ISC_TIME timeTzToTime(const ISC_TIME_TZ& timeTz, Callbacks* cb); diff --git a/src/common/classes/GenericMap.h b/src/common/classes/GenericMap.h index afb68edc52..456bf27ed8 100644 --- a/src/common/classes/GenericMap.h +++ b/src/common/classes/GenericMap.h @@ -283,6 +283,15 @@ public: return NULL; } + // If the key is not present, add it. Not synchronized. + ValueType* getOrPut(const KeyType& key) + { + if (auto value = get(key)) + return value; + + return put(key); + } + bool exist(const KeyType& key) const { return ConstTreeAccessor(&tree).locate(key); @@ -370,16 +379,16 @@ private: typedef GenericMap > > StringMap; template > -using NonPooledMap = GenericMap>, KeyComparator>; +using NonPooledMap = GenericMap, KeyComparator>; template > -using LeftPooledMap = GenericMap>, KeyComparator>; +using LeftPooledMap = GenericMap, KeyComparator>; template > -using RightPooledMap = GenericMap>, KeyComparator>; +using RightPooledMap = GenericMap, KeyComparator>; template > -using FullPooledMap = GenericMap>, KeyComparator>; +using FullPooledMap = GenericMap, KeyComparator>; } diff --git a/src/common/classes/RefCounted.h b/src/common/classes/RefCounted.h index 062afab43d..f9708e91b6 100644 --- a/src/common/classes/RefCounted.h +++ b/src/common/classes/RefCounted.h @@ -121,6 +121,12 @@ namespace Firebird r.ptr = nullptr; } + RefPtr(MemoryPool&, RefPtr&& r) + : ptr(r.ptr) + { + r.ptr = nullptr; + } + ~RefPtr() { if (ptr) diff --git a/src/common/classes/fb_pair.h b/src/common/classes/fb_pair.h index 0396624327..ac202a43ec 100644 --- a/src/common/classes/fb_pair.h +++ b/src/common/classes/fb_pair.h @@ -144,6 +144,18 @@ template } }; +template +using NonPooledPair = Pair>; + +template +using LeftPooledPair = Pair>; + +template +using RightPooledPair = Pair>; + +template +using FullPooledPair = Pair>; + template class FirstKey { diff --git a/src/common/classes/fb_string.h b/src/common/classes/fb_string.h index 31db8abdf1..99d227e0ba 100644 --- a/src/common/classes/fb_string.h +++ b/src/common/classes/fb_string.h @@ -662,7 +662,8 @@ namespace Firebird StringBase() : AbstractString(Comparator::getMaxLength()) {} StringBase(const StringType& v) : AbstractString(Comparator::getMaxLength(), v) {} StringBase(const void* s, size_type n) : AbstractString(Comparator::getMaxLength(), n, s) {} - StringBase(const_pointer s) : AbstractString(Comparator::getMaxLength(), static_cast(strlen(s)), s) {} + StringBase(const_pointer s) : + AbstractString(Comparator::getMaxLength(), static_cast(s ? strlen(s) : 0), s) {} explicit StringBase(const unsigned char* s) : AbstractString(Comparator::getMaxLength(), static_cast(strlen((char*)s)), (char*)s) {} StringBase(const MetaString& v) : AbstractString(Comparator::getMaxLength(), v) {} @@ -671,6 +672,8 @@ namespace Firebird AbstractString(Comparator::getMaxLength(), last - first, first) {} explicit StringBase(MemoryPool& p) : AbstractString(Comparator::getMaxLength(), p) {} StringBase(MemoryPool& p, const AbstractString& v) : AbstractString(Comparator::getMaxLength(), p, v) {} + StringBase(MemoryPool& p, const_pointer s) : + AbstractString(Comparator::getMaxLength(), p, s, static_cast(s ? strlen(s) : 0)) {} StringBase(MemoryPool& p, const char_type* s, size_type l) : AbstractString(Comparator::getMaxLength(), p, s, l) {} diff --git a/src/common/classes/objects_array.h b/src/common/classes/objects_array.h index d946b87976..e37223f9d6 100644 --- a/src/common/classes/objects_array.h +++ b/src/common/classes/objects_array.h @@ -214,6 +214,12 @@ namespace Firebird return inherited::add(dataL); } + size_type add(T&& item) + { + T* dataL = FB_NEW_POOL(this->getPool()) T(this->getPool(), std::move(item)); + return inherited::add(dataL); + } + T& add() { T* dataL = FB_NEW_POOL(this->getPool()) T(this->getPool()); diff --git a/src/dsql/ExprNodes.cpp b/src/dsql/ExprNodes.cpp index 21b64eec0a..41f3d572bb 100644 --- a/src/dsql/ExprNodes.cpp +++ b/src/dsql/ExprNodes.cpp @@ -6839,7 +6839,7 @@ dsc* FieldNode::execute(thread_db* tdbb, jrd_req* request) const { // Computed fields shouldn't be present at this point jrd_fld* field = MET_get_field(relation, fieldId); - fb_assert(field && !field->fld_computation); + fb_assert(!field || !field->fld_computation); } #endif diff --git a/src/dsql/Nodes.h b/src/dsql/Nodes.h index 6d5ec75e37..705e646e5f 100644 --- a/src/dsql/Nodes.h +++ b/src/dsql/Nodes.h @@ -1530,6 +1530,11 @@ public: return NULL; } + virtual bool isProfileAware() const + { + return true; + } + virtual const StmtNode* execute(thread_db* tdbb, jrd_req* request, ExeState* exeState) const = 0; public: diff --git a/src/dsql/StmtNodes.cpp b/src/dsql/StmtNodes.cpp index d92972245e..c9acb6649f 100644 --- a/src/dsql/StmtNodes.cpp +++ b/src/dsql/StmtNodes.cpp @@ -845,7 +845,7 @@ const StmtNode* CompoundStmtNode::execute(thread_db* tdbb, jrd_req* request, Exe { const NestConst* end = statements.end(); - if (onlyAssignments) + if (onlyAssignments && !request->req_attachment->isProfilerActive()) { if (request->req_operation == jrd_req::req_evaluate) { @@ -8714,6 +8714,10 @@ string ReturnNode::internalPrint(NodePrinter& printer) const void ReturnNode::genBlr(DsqlCompilerScratch* dsqlScratch) { dsqlScratch->appendUChar(blr_begin); + + if (hasLineColumn) + dsqlScratch->putDebugSrcInfo(line, column); + dsqlScratch->appendUChar(blr_assignment); GEN_expr(dsqlScratch, value); dsqlScratch->appendUChar(blr_variable); diff --git a/src/dsql/StmtNodes.h b/src/dsql/StmtNodes.h index b38aacd504..47c78bc093 100644 --- a/src/dsql/StmtNodes.h +++ b/src/dsql/StmtNodes.h @@ -279,6 +279,11 @@ public: public: static DmlNode* parse(thread_db* tdbb, MemoryPool& pool, CompilerScratch* csb, const UCHAR blrOp); + virtual bool isProfileAware() const + { + return false; + } + virtual Firebird::string internalPrint(NodePrinter& printer) const; virtual CompoundStmtNode* dsqlPass(DsqlCompilerScratch* dsqlScratch); virtual void genBlr(DsqlCompilerScratch* dsqlScratch); diff --git a/src/include/firebird/FirebirdInterface.idl b/src/include/firebird/FirebirdInterface.idl index 4c912c3346..f02a3ae33e 100644 --- a/src/include/firebird/FirebirdInterface.idl +++ b/src/include/firebird/FirebirdInterface.idl @@ -250,8 +250,9 @@ interface PluginManager : Versioned const uint TYPE_DB_CRYPT = 9; const uint TYPE_KEY_HOLDER = 10; const uint TYPE_REPLICATOR = 11; - const uint TYPE_COUNT = 12; // keep in sync - //// TODO: TYPE_COUNT is not count. And these constants starts from 1, different than DIR_* ones. + const uint TYPE_PROFILER = 12; + const uint TYPE_COUNT = 13; // keep in sync + //// TYPE_COUNT is not count. And these constants starts from 1, different than DIR_* ones. // Main function called by plugin modules in firebird_plugin() void registerPluginFactory(uint pluginType, const string defaultName, PluginFactory factory); @@ -1695,3 +1696,49 @@ interface ReplicatedSession : PluginBase void setSequence(Status status, const string name, int64 value); } +// Profiler interfaces + +interface ProfilerPlugin : PluginBase +{ + void init(Status status, Attachment attachment, Transaction transaction); + + ProfilerSession startSession(Status status, Transaction transaction, const string description, + ISC_TIMESTAMP_TZ timestamp); + + void flush(Status status, Transaction transaction); +} + +interface ProfilerSession : Disposable +{ + const uint FLAG_BEFORE_EVENTS = 0x1; + const uint FLAG_AFTER_EVENTS = 0x2; + + int64 getId(); + uint getFlags(); + + void finish(Status status, ISC_TIMESTAMP_TZ timestamp); + + //// FIXME: Add memory stats + + void defineStatement(Status status, int64 statementId, int64 parentStatementId, + const string type, const string packageName, const string routineName, const string sqlText); + + //// TODO: Add defineCursor with line/column + + void defineRecordSource(int64 statementId, uint cursorId, uint recSourceId, + const string accessPath, uint parentRecSourceId); + + void onRequestStart(Status status, int64 requestId, int64 statementId, int64 callerRequestId, + ISC_TIMESTAMP_TZ timestamp); + + void onRequestFinish(Status status, int64 requestId, ISC_TIMESTAMP_TZ timestamp); + + void beforePsqlLineColumn(int64 requestId, uint line, uint column); + void afterPsqlLineColumn(int64 requestId, uint line, uint column, uint64 runTime); + + void beforeRecordSourceOpen(int64 requestId, uint cursorId, uint recSourceId); + void afterRecordSourceOpen(int64 requestId, uint cursorId, uint recSourceId, uint64 runTime); + + void beforeRecordSourceGetRecord(int64 requestId, uint cursorId, uint recSourceId); + void afterRecordSourceGetRecord(int64 requestId, uint cursorId, uint recSourceId, uint64 runTime); +} diff --git a/src/include/firebird/IdlFbInterfaces.h b/src/include/firebird/IdlFbInterfaces.h index e16bede394..9fd497fcca 100644 --- a/src/include/firebird/IdlFbInterfaces.h +++ b/src/include/firebird/IdlFbInterfaces.h @@ -121,6 +121,8 @@ namespace Firebird class IReplicatedRecord; class IReplicatedTransaction; class IReplicatedSession; + class IProfilerPlugin; + class IProfilerSession; // Interfaces declarations @@ -825,7 +827,8 @@ namespace Firebird static const unsigned TYPE_DB_CRYPT = 9; static const unsigned TYPE_KEY_HOLDER = 10; static const unsigned TYPE_REPLICATOR = 11; - static const unsigned TYPE_COUNT = 12; + static const unsigned TYPE_PROFILER = 12; + static const unsigned TYPE_COUNT = 13; void registerPluginFactory(unsigned pluginType, const char* defaultName, IPluginFactory* factory) { @@ -6640,6 +6643,164 @@ namespace Firebird } }; + class IProfilerPlugin : public IPluginBase + { + public: + struct VTable : public IPluginBase::VTable + { + void (CLOOP_CARG *init)(IProfilerPlugin* self, IStatus* status, IAttachment* attachment, ITransaction* transaction) throw(); + IProfilerSession* (CLOOP_CARG *startSession)(IProfilerPlugin* self, IStatus* status, ITransaction* transaction, const char* description, ISC_TIMESTAMP_TZ timestamp) throw(); + void (CLOOP_CARG *flush)(IProfilerPlugin* self, IStatus* status, ITransaction* transaction) throw(); + }; + + protected: + IProfilerPlugin(DoNotInherit) + : IPluginBase(DoNotInherit()) + { + } + + ~IProfilerPlugin() + { + } + + public: + static const unsigned VERSION = 4; + + template void init(StatusType* status, IAttachment* attachment, ITransaction* transaction) + { + StatusType::clearException(status); + static_cast(this->cloopVTable)->init(this, status, attachment, transaction); + StatusType::checkException(status); + } + + template IProfilerSession* startSession(StatusType* status, ITransaction* transaction, const char* description, ISC_TIMESTAMP_TZ timestamp) + { + StatusType::clearException(status); + IProfilerSession* ret = static_cast(this->cloopVTable)->startSession(this, status, transaction, description, timestamp); + StatusType::checkException(status); + return ret; + } + + template void flush(StatusType* status, ITransaction* transaction) + { + StatusType::clearException(status); + static_cast(this->cloopVTable)->flush(this, status, transaction); + StatusType::checkException(status); + } + }; + + class IProfilerSession : public IDisposable + { + public: + struct VTable : public IDisposable::VTable + { + ISC_INT64 (CLOOP_CARG *getId)(IProfilerSession* self) throw(); + unsigned (CLOOP_CARG *getFlags)(IProfilerSession* self) throw(); + void (CLOOP_CARG *finish)(IProfilerSession* self, IStatus* status, ISC_TIMESTAMP_TZ timestamp) throw(); + void (CLOOP_CARG *defineStatement)(IProfilerSession* self, IStatus* status, ISC_INT64 statementId, ISC_INT64 parentStatementId, const char* type, const char* packageName, const char* routineName, const char* sqlText) throw(); + void (CLOOP_CARG *defineRecordSource)(IProfilerSession* self, ISC_INT64 statementId, unsigned cursorId, unsigned recSourceId, const char* accessPath, unsigned parentRecSourceId) throw(); + void (CLOOP_CARG *onRequestStart)(IProfilerSession* self, IStatus* status, ISC_INT64 requestId, ISC_INT64 statementId, ISC_INT64 callerRequestId, ISC_TIMESTAMP_TZ timestamp) throw(); + void (CLOOP_CARG *onRequestFinish)(IProfilerSession* self, IStatus* status, ISC_INT64 requestId, ISC_TIMESTAMP_TZ timestamp) throw(); + void (CLOOP_CARG *beforePsqlLineColumn)(IProfilerSession* self, ISC_INT64 requestId, unsigned line, unsigned column) throw(); + void (CLOOP_CARG *afterPsqlLineColumn)(IProfilerSession* self, ISC_INT64 requestId, unsigned line, unsigned column, ISC_UINT64 runTime) throw(); + void (CLOOP_CARG *beforeRecordSourceOpen)(IProfilerSession* self, ISC_INT64 requestId, unsigned cursorId, unsigned recSourceId) throw(); + void (CLOOP_CARG *afterRecordSourceOpen)(IProfilerSession* self, ISC_INT64 requestId, unsigned cursorId, unsigned recSourceId, ISC_UINT64 runTime) throw(); + void (CLOOP_CARG *beforeRecordSourceGetRecord)(IProfilerSession* self, ISC_INT64 requestId, unsigned cursorId, unsigned recSourceId) throw(); + void (CLOOP_CARG *afterRecordSourceGetRecord)(IProfilerSession* self, ISC_INT64 requestId, unsigned cursorId, unsigned recSourceId, ISC_UINT64 runTime) throw(); + }; + + protected: + IProfilerSession(DoNotInherit) + : IDisposable(DoNotInherit()) + { + } + + ~IProfilerSession() + { + } + + public: + static const unsigned VERSION = 3; + + static const unsigned FLAG_BEFORE_EVENTS = 0x1; + static const unsigned FLAG_AFTER_EVENTS = 0x2; + + ISC_INT64 getId() + { + ISC_INT64 ret = static_cast(this->cloopVTable)->getId(this); + return ret; + } + + unsigned getFlags() + { + unsigned ret = static_cast(this->cloopVTable)->getFlags(this); + return ret; + } + + template void finish(StatusType* status, ISC_TIMESTAMP_TZ timestamp) + { + StatusType::clearException(status); + static_cast(this->cloopVTable)->finish(this, status, timestamp); + StatusType::checkException(status); + } + + template void defineStatement(StatusType* status, ISC_INT64 statementId, ISC_INT64 parentStatementId, const char* type, const char* packageName, const char* routineName, const char* sqlText) + { + StatusType::clearException(status); + static_cast(this->cloopVTable)->defineStatement(this, status, statementId, parentStatementId, type, packageName, routineName, sqlText); + StatusType::checkException(status); + } + + void defineRecordSource(ISC_INT64 statementId, unsigned cursorId, unsigned recSourceId, const char* accessPath, unsigned parentRecSourceId) + { + static_cast(this->cloopVTable)->defineRecordSource(this, statementId, cursorId, recSourceId, accessPath, parentRecSourceId); + } + + template void onRequestStart(StatusType* status, ISC_INT64 requestId, ISC_INT64 statementId, ISC_INT64 callerRequestId, ISC_TIMESTAMP_TZ timestamp) + { + StatusType::clearException(status); + static_cast(this->cloopVTable)->onRequestStart(this, status, requestId, statementId, callerRequestId, timestamp); + StatusType::checkException(status); + } + + template void onRequestFinish(StatusType* status, ISC_INT64 requestId, ISC_TIMESTAMP_TZ timestamp) + { + StatusType::clearException(status); + static_cast(this->cloopVTable)->onRequestFinish(this, status, requestId, timestamp); + StatusType::checkException(status); + } + + void beforePsqlLineColumn(ISC_INT64 requestId, unsigned line, unsigned column) + { + static_cast(this->cloopVTable)->beforePsqlLineColumn(this, requestId, line, column); + } + + void afterPsqlLineColumn(ISC_INT64 requestId, unsigned line, unsigned column, ISC_UINT64 runTime) + { + static_cast(this->cloopVTable)->afterPsqlLineColumn(this, requestId, line, column, runTime); + } + + void beforeRecordSourceOpen(ISC_INT64 requestId, unsigned cursorId, unsigned recSourceId) + { + static_cast(this->cloopVTable)->beforeRecordSourceOpen(this, requestId, cursorId, recSourceId); + } + + void afterRecordSourceOpen(ISC_INT64 requestId, unsigned cursorId, unsigned recSourceId, ISC_UINT64 runTime) + { + static_cast(this->cloopVTable)->afterRecordSourceOpen(this, requestId, cursorId, recSourceId, runTime); + } + + void beforeRecordSourceGetRecord(ISC_INT64 requestId, unsigned cursorId, unsigned recSourceId) + { + static_cast(this->cloopVTable)->beforeRecordSourceGetRecord(this, requestId, cursorId, recSourceId); + } + + void afterRecordSourceGetRecord(ISC_INT64 requestId, unsigned cursorId, unsigned recSourceId, ISC_UINT64 runTime) + { + static_cast(this->cloopVTable)->afterRecordSourceGetRecord(this, requestId, cursorId, recSourceId, runTime); + } + }; + // Interfaces implementations template @@ -19768,6 +19929,384 @@ namespace Firebird virtual void cleanupTransaction(StatusType* status, ISC_INT64 number) = 0; virtual void setSequence(StatusType* status, const char* name, ISC_INT64 value) = 0; }; + + template + class IProfilerPluginBaseImpl : public Base + { + public: + typedef IProfilerPlugin Declaration; + + IProfilerPluginBaseImpl(DoNotInherit = DoNotInherit()) + { + static struct VTableImpl : Base::VTable + { + VTableImpl() + { + this->version = Base::VERSION; + this->addRef = &Name::cloopaddRefDispatcher; + this->release = &Name::cloopreleaseDispatcher; + this->setOwner = &Name::cloopsetOwnerDispatcher; + this->getOwner = &Name::cloopgetOwnerDispatcher; + this->init = &Name::cloopinitDispatcher; + this->startSession = &Name::cloopstartSessionDispatcher; + this->flush = &Name::cloopflushDispatcher; + } + } vTable; + + this->cloopVTable = &vTable; + } + + static void CLOOP_CARG cloopinitDispatcher(IProfilerPlugin* self, IStatus* status, IAttachment* attachment, ITransaction* transaction) throw() + { + StatusType status2(status); + + try + { + static_cast(self)->Name::init(&status2, attachment, transaction); + } + catch (...) + { + StatusType::catchException(&status2); + } + } + + static IProfilerSession* CLOOP_CARG cloopstartSessionDispatcher(IProfilerPlugin* self, IStatus* status, ITransaction* transaction, const char* description, ISC_TIMESTAMP_TZ timestamp) throw() + { + StatusType status2(status); + + try + { + return static_cast(self)->Name::startSession(&status2, transaction, description, timestamp); + } + catch (...) + { + StatusType::catchException(&status2); + return static_cast(0); + } + } + + static void CLOOP_CARG cloopflushDispatcher(IProfilerPlugin* self, IStatus* status, ITransaction* transaction) throw() + { + StatusType status2(status); + + try + { + static_cast(self)->Name::flush(&status2, transaction); + } + catch (...) + { + StatusType::catchException(&status2); + } + } + + static void CLOOP_CARG cloopsetOwnerDispatcher(IPluginBase* self, IReferenceCounted* r) throw() + { + try + { + static_cast(self)->Name::setOwner(r); + } + catch (...) + { + StatusType::catchException(0); + } + } + + static IReferenceCounted* CLOOP_CARG cloopgetOwnerDispatcher(IPluginBase* self) throw() + { + try + { + return static_cast(self)->Name::getOwner(); + } + catch (...) + { + StatusType::catchException(0); + return static_cast(0); + } + } + + static void CLOOP_CARG cloopaddRefDispatcher(IReferenceCounted* self) throw() + { + try + { + static_cast(self)->Name::addRef(); + } + catch (...) + { + StatusType::catchException(0); + } + } + + static int CLOOP_CARG cloopreleaseDispatcher(IReferenceCounted* self) throw() + { + try + { + return static_cast(self)->Name::release(); + } + catch (...) + { + StatusType::catchException(0); + return static_cast(0); + } + } + }; + + template > > > > > > + class IProfilerPluginImpl : public IProfilerPluginBaseImpl + { + protected: + IProfilerPluginImpl(DoNotInherit = DoNotInherit()) + { + } + + public: + virtual ~IProfilerPluginImpl() + { + } + + virtual void init(StatusType* status, IAttachment* attachment, ITransaction* transaction) = 0; + virtual IProfilerSession* startSession(StatusType* status, ITransaction* transaction, const char* description, ISC_TIMESTAMP_TZ timestamp) = 0; + virtual void flush(StatusType* status, ITransaction* transaction) = 0; + }; + + template + class IProfilerSessionBaseImpl : public Base + { + public: + typedef IProfilerSession Declaration; + + IProfilerSessionBaseImpl(DoNotInherit = DoNotInherit()) + { + static struct VTableImpl : Base::VTable + { + VTableImpl() + { + this->version = Base::VERSION; + this->dispose = &Name::cloopdisposeDispatcher; + this->getId = &Name::cloopgetIdDispatcher; + this->getFlags = &Name::cloopgetFlagsDispatcher; + this->finish = &Name::cloopfinishDispatcher; + this->defineStatement = &Name::cloopdefineStatementDispatcher; + this->defineRecordSource = &Name::cloopdefineRecordSourceDispatcher; + this->onRequestStart = &Name::clooponRequestStartDispatcher; + this->onRequestFinish = &Name::clooponRequestFinishDispatcher; + this->beforePsqlLineColumn = &Name::cloopbeforePsqlLineColumnDispatcher; + this->afterPsqlLineColumn = &Name::cloopafterPsqlLineColumnDispatcher; + this->beforeRecordSourceOpen = &Name::cloopbeforeRecordSourceOpenDispatcher; + this->afterRecordSourceOpen = &Name::cloopafterRecordSourceOpenDispatcher; + this->beforeRecordSourceGetRecord = &Name::cloopbeforeRecordSourceGetRecordDispatcher; + this->afterRecordSourceGetRecord = &Name::cloopafterRecordSourceGetRecordDispatcher; + } + } vTable; + + this->cloopVTable = &vTable; + } + + static ISC_INT64 CLOOP_CARG cloopgetIdDispatcher(IProfilerSession* self) throw() + { + try + { + return static_cast(self)->Name::getId(); + } + catch (...) + { + StatusType::catchException(0); + return static_cast(0); + } + } + + static unsigned CLOOP_CARG cloopgetFlagsDispatcher(IProfilerSession* self) throw() + { + try + { + return static_cast(self)->Name::getFlags(); + } + catch (...) + { + StatusType::catchException(0); + return static_cast(0); + } + } + + static void CLOOP_CARG cloopfinishDispatcher(IProfilerSession* self, IStatus* status, ISC_TIMESTAMP_TZ timestamp) throw() + { + StatusType status2(status); + + try + { + static_cast(self)->Name::finish(&status2, timestamp); + } + catch (...) + { + StatusType::catchException(&status2); + } + } + + static void CLOOP_CARG cloopdefineStatementDispatcher(IProfilerSession* self, IStatus* status, ISC_INT64 statementId, ISC_INT64 parentStatementId, const char* type, const char* packageName, const char* routineName, const char* sqlText) throw() + { + StatusType status2(status); + + try + { + static_cast(self)->Name::defineStatement(&status2, statementId, parentStatementId, type, packageName, routineName, sqlText); + } + catch (...) + { + StatusType::catchException(&status2); + } + } + + static void CLOOP_CARG cloopdefineRecordSourceDispatcher(IProfilerSession* self, ISC_INT64 statementId, unsigned cursorId, unsigned recSourceId, const char* accessPath, unsigned parentRecSourceId) throw() + { + try + { + static_cast(self)->Name::defineRecordSource(statementId, cursorId, recSourceId, accessPath, parentRecSourceId); + } + catch (...) + { + StatusType::catchException(0); + } + } + + static void CLOOP_CARG clooponRequestStartDispatcher(IProfilerSession* self, IStatus* status, ISC_INT64 requestId, ISC_INT64 statementId, ISC_INT64 callerRequestId, ISC_TIMESTAMP_TZ timestamp) throw() + { + StatusType status2(status); + + try + { + static_cast(self)->Name::onRequestStart(&status2, requestId, statementId, callerRequestId, timestamp); + } + catch (...) + { + StatusType::catchException(&status2); + } + } + + static void CLOOP_CARG clooponRequestFinishDispatcher(IProfilerSession* self, IStatus* status, ISC_INT64 requestId, ISC_TIMESTAMP_TZ timestamp) throw() + { + StatusType status2(status); + + try + { + static_cast(self)->Name::onRequestFinish(&status2, requestId, timestamp); + } + catch (...) + { + StatusType::catchException(&status2); + } + } + + static void CLOOP_CARG cloopbeforePsqlLineColumnDispatcher(IProfilerSession* self, ISC_INT64 requestId, unsigned line, unsigned column) throw() + { + try + { + static_cast(self)->Name::beforePsqlLineColumn(requestId, line, column); + } + catch (...) + { + StatusType::catchException(0); + } + } + + static void CLOOP_CARG cloopafterPsqlLineColumnDispatcher(IProfilerSession* self, ISC_INT64 requestId, unsigned line, unsigned column, ISC_UINT64 runTime) throw() + { + try + { + static_cast(self)->Name::afterPsqlLineColumn(requestId, line, column, runTime); + } + catch (...) + { + StatusType::catchException(0); + } + } + + static void CLOOP_CARG cloopbeforeRecordSourceOpenDispatcher(IProfilerSession* self, ISC_INT64 requestId, unsigned cursorId, unsigned recSourceId) throw() + { + try + { + static_cast(self)->Name::beforeRecordSourceOpen(requestId, cursorId, recSourceId); + } + catch (...) + { + StatusType::catchException(0); + } + } + + static void CLOOP_CARG cloopafterRecordSourceOpenDispatcher(IProfilerSession* self, ISC_INT64 requestId, unsigned cursorId, unsigned recSourceId, ISC_UINT64 runTime) throw() + { + try + { + static_cast(self)->Name::afterRecordSourceOpen(requestId, cursorId, recSourceId, runTime); + } + catch (...) + { + StatusType::catchException(0); + } + } + + static void CLOOP_CARG cloopbeforeRecordSourceGetRecordDispatcher(IProfilerSession* self, ISC_INT64 requestId, unsigned cursorId, unsigned recSourceId) throw() + { + try + { + static_cast(self)->Name::beforeRecordSourceGetRecord(requestId, cursorId, recSourceId); + } + catch (...) + { + StatusType::catchException(0); + } + } + + static void CLOOP_CARG cloopafterRecordSourceGetRecordDispatcher(IProfilerSession* self, ISC_INT64 requestId, unsigned cursorId, unsigned recSourceId, ISC_UINT64 runTime) throw() + { + try + { + static_cast(self)->Name::afterRecordSourceGetRecord(requestId, cursorId, recSourceId, runTime); + } + catch (...) + { + StatusType::catchException(0); + } + } + + static void CLOOP_CARG cloopdisposeDispatcher(IDisposable* self) throw() + { + try + { + static_cast(self)->Name::dispose(); + } + catch (...) + { + StatusType::catchException(0); + } + } + }; + + template > > > > + class IProfilerSessionImpl : public IProfilerSessionBaseImpl + { + protected: + IProfilerSessionImpl(DoNotInherit = DoNotInherit()) + { + } + + public: + virtual ~IProfilerSessionImpl() + { + } + + virtual ISC_INT64 getId() = 0; + virtual unsigned getFlags() = 0; + virtual void finish(StatusType* status, ISC_TIMESTAMP_TZ timestamp) = 0; + virtual void defineStatement(StatusType* status, ISC_INT64 statementId, ISC_INT64 parentStatementId, const char* type, const char* packageName, const char* routineName, const char* sqlText) = 0; + virtual void defineRecordSource(ISC_INT64 statementId, unsigned cursorId, unsigned recSourceId, const char* accessPath, unsigned parentRecSourceId) = 0; + virtual void onRequestStart(StatusType* status, ISC_INT64 requestId, ISC_INT64 statementId, ISC_INT64 callerRequestId, ISC_TIMESTAMP_TZ timestamp) = 0; + virtual void onRequestFinish(StatusType* status, ISC_INT64 requestId, ISC_TIMESTAMP_TZ timestamp) = 0; + virtual void beforePsqlLineColumn(ISC_INT64 requestId, unsigned line, unsigned column) = 0; + virtual void afterPsqlLineColumn(ISC_INT64 requestId, unsigned line, unsigned column, ISC_UINT64 runTime) = 0; + virtual void beforeRecordSourceOpen(ISC_INT64 requestId, unsigned cursorId, unsigned recSourceId) = 0; + virtual void afterRecordSourceOpen(ISC_INT64 requestId, unsigned cursorId, unsigned recSourceId, ISC_UINT64 runTime) = 0; + virtual void beforeRecordSourceGetRecord(ISC_INT64 requestId, unsigned cursorId, unsigned recSourceId) = 0; + virtual void afterRecordSourceGetRecord(ISC_INT64 requestId, unsigned cursorId, unsigned recSourceId, ISC_UINT64 runTime) = 0; + }; }; diff --git a/src/include/gen/Firebird.pas b/src/include/gen/Firebird.pas index 6b512ffd24..019fc696d2 100644 --- a/src/include/gen/Firebird.pas +++ b/src/include/gen/Firebird.pas @@ -111,6 +111,8 @@ type IReplicatedRecord = class; IReplicatedTransaction = class; IReplicatedSession = class; + IProfilerPlugin = class; + IProfilerSession = class; FbException = class(Exception) public @@ -712,6 +714,22 @@ type IReplicatedSession_startTransactionPtr = function(this: IReplicatedSession; status: IStatus; transaction: ITransaction; number: Int64): IReplicatedTransaction; cdecl; IReplicatedSession_cleanupTransactionPtr = procedure(this: IReplicatedSession; status: IStatus; number: Int64); cdecl; IReplicatedSession_setSequencePtr = procedure(this: IReplicatedSession; status: IStatus; name: PAnsiChar; value: Int64); cdecl; + IProfilerPlugin_initPtr = procedure(this: IProfilerPlugin; status: IStatus; attachment: IAttachment; transaction: ITransaction); cdecl; + IProfilerPlugin_startSessionPtr = function(this: IProfilerPlugin; status: IStatus; transaction: ITransaction; description: PAnsiChar; timestamp: ISC_TIMESTAMP_TZ): IProfilerSession; cdecl; + IProfilerPlugin_flushPtr = procedure(this: IProfilerPlugin; status: IStatus; transaction: ITransaction); cdecl; + IProfilerSession_getIdPtr = function(this: IProfilerSession): Int64; cdecl; + IProfilerSession_getFlagsPtr = function(this: IProfilerSession): Cardinal; cdecl; + IProfilerSession_finishPtr = procedure(this: IProfilerSession; status: IStatus; timestamp: ISC_TIMESTAMP_TZ); cdecl; + IProfilerSession_defineStatementPtr = procedure(this: IProfilerSession; status: IStatus; statementId: Int64; parentStatementId: Int64; type_: PAnsiChar; packageName: PAnsiChar; routineName: PAnsiChar; sqlText: PAnsiChar); cdecl; + IProfilerSession_defineRecordSourcePtr = procedure(this: IProfilerSession; statementId: Int64; cursorId: Cardinal; recSourceId: Cardinal; accessPath: PAnsiChar; parentRecSourceId: Cardinal); cdecl; + IProfilerSession_onRequestStartPtr = procedure(this: IProfilerSession; status: IStatus; requestId: Int64; statementId: Int64; callerRequestId: Int64; timestamp: ISC_TIMESTAMP_TZ); cdecl; + IProfilerSession_onRequestFinishPtr = procedure(this: IProfilerSession; status: IStatus; requestId: Int64; timestamp: ISC_TIMESTAMP_TZ); cdecl; + IProfilerSession_beforePsqlLineColumnPtr = procedure(this: IProfilerSession; requestId: Int64; line: Cardinal; column: Cardinal); cdecl; + IProfilerSession_afterPsqlLineColumnPtr = procedure(this: IProfilerSession; requestId: Int64; line: Cardinal; column: Cardinal; runTime: QWord); cdecl; + IProfilerSession_beforeRecordSourceOpenPtr = procedure(this: IProfilerSession; requestId: Int64; cursorId: Cardinal; recSourceId: Cardinal); cdecl; + IProfilerSession_afterRecordSourceOpenPtr = procedure(this: IProfilerSession; requestId: Int64; cursorId: Cardinal; recSourceId: Cardinal; runTime: QWord); cdecl; + IProfilerSession_beforeRecordSourceGetRecordPtr = procedure(this: IProfilerSession; requestId: Int64; cursorId: Cardinal; recSourceId: Cardinal); cdecl; + IProfilerSession_afterRecordSourceGetRecordPtr = procedure(this: IProfilerSession; requestId: Int64; cursorId: Cardinal; recSourceId: Cardinal; runTime: QWord); cdecl; VersionedVTable = class version: NativeInt; @@ -1079,7 +1097,8 @@ type const TYPE_DB_CRYPT = Cardinal(9); const TYPE_KEY_HOLDER = Cardinal(10); const TYPE_REPLICATOR = Cardinal(11); - const TYPE_COUNT = Cardinal(12); + const TYPE_PROFILER = Cardinal(12); + const TYPE_COUNT = Cardinal(13); procedure registerPluginFactory(pluginType: Cardinal; defaultName: PAnsiChar; factory: IPluginFactory); procedure registerModule(cleanup: IPluginModule); @@ -3746,6 +3765,87 @@ type procedure setSequence(status: IStatus; name: PAnsiChar; value: Int64); virtual; abstract; end; + ProfilerPluginVTable = class(PluginBaseVTable) + init: IProfilerPlugin_initPtr; + startSession: IProfilerPlugin_startSessionPtr; + flush: IProfilerPlugin_flushPtr; + end; + + IProfilerPlugin = class(IPluginBase) + const VERSION = 4; + + procedure init(status: IStatus; attachment: IAttachment; transaction: ITransaction); + function startSession(status: IStatus; transaction: ITransaction; description: PAnsiChar; timestamp: ISC_TIMESTAMP_TZ): IProfilerSession; + procedure flush(status: IStatus; transaction: ITransaction); + end; + + IProfilerPluginImpl = class(IProfilerPlugin) + constructor create; + + procedure addRef(); virtual; abstract; + function release(): Integer; virtual; abstract; + procedure setOwner(r: IReferenceCounted); virtual; abstract; + function getOwner(): IReferenceCounted; virtual; abstract; + procedure init(status: IStatus; attachment: IAttachment; transaction: ITransaction); virtual; abstract; + function startSession(status: IStatus; transaction: ITransaction; description: PAnsiChar; timestamp: ISC_TIMESTAMP_TZ): IProfilerSession; virtual; abstract; + procedure flush(status: IStatus; transaction: ITransaction); virtual; abstract; + end; + + ProfilerSessionVTable = class(DisposableVTable) + getId: IProfilerSession_getIdPtr; + getFlags: IProfilerSession_getFlagsPtr; + finish: IProfilerSession_finishPtr; + defineStatement: IProfilerSession_defineStatementPtr; + defineRecordSource: IProfilerSession_defineRecordSourcePtr; + onRequestStart: IProfilerSession_onRequestStartPtr; + onRequestFinish: IProfilerSession_onRequestFinishPtr; + beforePsqlLineColumn: IProfilerSession_beforePsqlLineColumnPtr; + afterPsqlLineColumn: IProfilerSession_afterPsqlLineColumnPtr; + beforeRecordSourceOpen: IProfilerSession_beforeRecordSourceOpenPtr; + afterRecordSourceOpen: IProfilerSession_afterRecordSourceOpenPtr; + beforeRecordSourceGetRecord: IProfilerSession_beforeRecordSourceGetRecordPtr; + afterRecordSourceGetRecord: IProfilerSession_afterRecordSourceGetRecordPtr; + end; + + IProfilerSession = class(IDisposable) + const VERSION = 3; + const FLAG_BEFORE_EVENTS = Cardinal($1); + const FLAG_AFTER_EVENTS = Cardinal($2); + + function getId(): Int64; + function getFlags(): Cardinal; + procedure finish(status: IStatus; timestamp: ISC_TIMESTAMP_TZ); + procedure defineStatement(status: IStatus; statementId: Int64; parentStatementId: Int64; type_: PAnsiChar; packageName: PAnsiChar; routineName: PAnsiChar; sqlText: PAnsiChar); + procedure defineRecordSource(statementId: Int64; cursorId: Cardinal; recSourceId: Cardinal; accessPath: PAnsiChar; parentRecSourceId: Cardinal); + procedure onRequestStart(status: IStatus; requestId: Int64; statementId: Int64; callerRequestId: Int64; timestamp: ISC_TIMESTAMP_TZ); + procedure onRequestFinish(status: IStatus; requestId: Int64; timestamp: ISC_TIMESTAMP_TZ); + procedure beforePsqlLineColumn(requestId: Int64; line: Cardinal; column: Cardinal); + procedure afterPsqlLineColumn(requestId: Int64; line: Cardinal; column: Cardinal; runTime: QWord); + procedure beforeRecordSourceOpen(requestId: Int64; cursorId: Cardinal; recSourceId: Cardinal); + procedure afterRecordSourceOpen(requestId: Int64; cursorId: Cardinal; recSourceId: Cardinal; runTime: QWord); + procedure beforeRecordSourceGetRecord(requestId: Int64; cursorId: Cardinal; recSourceId: Cardinal); + procedure afterRecordSourceGetRecord(requestId: Int64; cursorId: Cardinal; recSourceId: Cardinal; runTime: QWord); + end; + + IProfilerSessionImpl = class(IProfilerSession) + constructor create; + + procedure dispose(); virtual; abstract; + function getId(): Int64; virtual; abstract; + function getFlags(): Cardinal; virtual; abstract; + procedure finish(status: IStatus; timestamp: ISC_TIMESTAMP_TZ); virtual; abstract; + procedure defineStatement(status: IStatus; statementId: Int64; parentStatementId: Int64; type_: PAnsiChar; packageName: PAnsiChar; routineName: PAnsiChar; sqlText: PAnsiChar); virtual; abstract; + procedure defineRecordSource(statementId: Int64; cursorId: Cardinal; recSourceId: Cardinal; accessPath: PAnsiChar; parentRecSourceId: Cardinal); virtual; abstract; + procedure onRequestStart(status: IStatus; requestId: Int64; statementId: Int64; callerRequestId: Int64; timestamp: ISC_TIMESTAMP_TZ); virtual; abstract; + procedure onRequestFinish(status: IStatus; requestId: Int64; timestamp: ISC_TIMESTAMP_TZ); virtual; abstract; + procedure beforePsqlLineColumn(requestId: Int64; line: Cardinal; column: Cardinal); virtual; abstract; + procedure afterPsqlLineColumn(requestId: Int64; line: Cardinal; column: Cardinal; runTime: QWord); virtual; abstract; + procedure beforeRecordSourceOpen(requestId: Int64; cursorId: Cardinal; recSourceId: Cardinal); virtual; abstract; + procedure afterRecordSourceOpen(requestId: Int64; cursorId: Cardinal; recSourceId: Cardinal; runTime: QWord); virtual; abstract; + procedure beforeRecordSourceGetRecord(requestId: Int64; cursorId: Cardinal; recSourceId: Cardinal); virtual; abstract; + procedure afterRecordSourceGetRecord(requestId: Int64; cursorId: Cardinal; recSourceId: Cardinal; runTime: QWord); virtual; abstract; + end; + function fb_get_master_interface : IMaster; cdecl; external 'fbclient'; const @@ -8841,6 +8941,93 @@ begin FbException.checkException(status); end; +procedure IProfilerPlugin.init(status: IStatus; attachment: IAttachment; transaction: ITransaction); +begin + ProfilerPluginVTable(vTable).init(Self, status, attachment, transaction); + FbException.checkException(status); +end; + +function IProfilerPlugin.startSession(status: IStatus; transaction: ITransaction; description: PAnsiChar; timestamp: ISC_TIMESTAMP_TZ): IProfilerSession; +begin + Result := ProfilerPluginVTable(vTable).startSession(Self, status, transaction, description, timestamp); + FbException.checkException(status); +end; + +procedure IProfilerPlugin.flush(status: IStatus; transaction: ITransaction); +begin + ProfilerPluginVTable(vTable).flush(Self, status, transaction); + FbException.checkException(status); +end; + +function IProfilerSession.getId(): Int64; +begin + Result := ProfilerSessionVTable(vTable).getId(Self); +end; + +function IProfilerSession.getFlags(): Cardinal; +begin + Result := ProfilerSessionVTable(vTable).getFlags(Self); +end; + +procedure IProfilerSession.finish(status: IStatus; timestamp: ISC_TIMESTAMP_TZ); +begin + ProfilerSessionVTable(vTable).finish(Self, status, timestamp); + FbException.checkException(status); +end; + +procedure IProfilerSession.defineStatement(status: IStatus; statementId: Int64; parentStatementId: Int64; type_: PAnsiChar; packageName: PAnsiChar; routineName: PAnsiChar; sqlText: PAnsiChar); +begin + ProfilerSessionVTable(vTable).defineStatement(Self, status, statementId, parentStatementId, type_, packageName, routineName, sqlText); + FbException.checkException(status); +end; + +procedure IProfilerSession.defineRecordSource(statementId: Int64; cursorId: Cardinal; recSourceId: Cardinal; accessPath: PAnsiChar; parentRecSourceId: Cardinal); +begin + ProfilerSessionVTable(vTable).defineRecordSource(Self, statementId, cursorId, recSourceId, accessPath, parentRecSourceId); +end; + +procedure IProfilerSession.onRequestStart(status: IStatus; requestId: Int64; statementId: Int64; callerRequestId: Int64; timestamp: ISC_TIMESTAMP_TZ); +begin + ProfilerSessionVTable(vTable).onRequestStart(Self, status, requestId, statementId, callerRequestId, timestamp); + FbException.checkException(status); +end; + +procedure IProfilerSession.onRequestFinish(status: IStatus; requestId: Int64; timestamp: ISC_TIMESTAMP_TZ); +begin + ProfilerSessionVTable(vTable).onRequestFinish(Self, status, requestId, timestamp); + FbException.checkException(status); +end; + +procedure IProfilerSession.beforePsqlLineColumn(requestId: Int64; line: Cardinal; column: Cardinal); +begin + ProfilerSessionVTable(vTable).beforePsqlLineColumn(Self, requestId, line, column); +end; + +procedure IProfilerSession.afterPsqlLineColumn(requestId: Int64; line: Cardinal; column: Cardinal; runTime: QWord); +begin + ProfilerSessionVTable(vTable).afterPsqlLineColumn(Self, requestId, line, column, runTime); +end; + +procedure IProfilerSession.beforeRecordSourceOpen(requestId: Int64; cursorId: Cardinal; recSourceId: Cardinal); +begin + ProfilerSessionVTable(vTable).beforeRecordSourceOpen(Self, requestId, cursorId, recSourceId); +end; + +procedure IProfilerSession.afterRecordSourceOpen(requestId: Int64; cursorId: Cardinal; recSourceId: Cardinal; runTime: QWord); +begin + ProfilerSessionVTable(vTable).afterRecordSourceOpen(Self, requestId, cursorId, recSourceId, runTime); +end; + +procedure IProfilerSession.beforeRecordSourceGetRecord(requestId: Int64; cursorId: Cardinal; recSourceId: Cardinal); +begin + ProfilerSessionVTable(vTable).beforeRecordSourceGetRecord(Self, requestId, cursorId, recSourceId); +end; + +procedure IProfilerSession.afterRecordSourceGetRecord(requestId: Int64; cursorId: Cardinal; recSourceId: Cardinal; runTime: QWord); +begin + ProfilerSessionVTable(vTable).afterRecordSourceGetRecord(Self, requestId, cursorId, recSourceId, runTime); +end; + var IVersionedImpl_vTable: VersionedVTable; @@ -15343,6 +15530,211 @@ begin vTable := IReplicatedSessionImpl_vTable; end; +procedure IProfilerPluginImpl_addRefDispatcher(this: IProfilerPlugin); cdecl; +begin + try + IProfilerPluginImpl(this).addRef(); + except + on e: Exception do FbException.catchException(nil, e); + end +end; + +function IProfilerPluginImpl_releaseDispatcher(this: IProfilerPlugin): Integer; cdecl; +begin + try + Result := IProfilerPluginImpl(this).release(); + except + on e: Exception do FbException.catchException(nil, e); + end +end; + +procedure IProfilerPluginImpl_setOwnerDispatcher(this: IProfilerPlugin; r: IReferenceCounted); cdecl; +begin + try + IProfilerPluginImpl(this).setOwner(r); + except + on e: Exception do FbException.catchException(nil, e); + end +end; + +function IProfilerPluginImpl_getOwnerDispatcher(this: IProfilerPlugin): IReferenceCounted; cdecl; +begin + try + Result := IProfilerPluginImpl(this).getOwner(); + except + on e: Exception do FbException.catchException(nil, e); + end +end; + +procedure IProfilerPluginImpl_initDispatcher(this: IProfilerPlugin; status: IStatus; attachment: IAttachment; transaction: ITransaction); cdecl; +begin + try + IProfilerPluginImpl(this).init(status, attachment, transaction); + except + on e: Exception do FbException.catchException(status, e); + end +end; + +function IProfilerPluginImpl_startSessionDispatcher(this: IProfilerPlugin; status: IStatus; transaction: ITransaction; description: PAnsiChar; timestamp: ISC_TIMESTAMP_TZ): IProfilerSession; cdecl; +begin + try + Result := IProfilerPluginImpl(this).startSession(status, transaction, description, timestamp); + except + on e: Exception do FbException.catchException(status, e); + end +end; + +procedure IProfilerPluginImpl_flushDispatcher(this: IProfilerPlugin; status: IStatus; transaction: ITransaction); cdecl; +begin + try + IProfilerPluginImpl(this).flush(status, transaction); + except + on e: Exception do FbException.catchException(status, e); + end +end; + +var + IProfilerPluginImpl_vTable: ProfilerPluginVTable; + +constructor IProfilerPluginImpl.create; +begin + vTable := IProfilerPluginImpl_vTable; +end; + +procedure IProfilerSessionImpl_disposeDispatcher(this: IProfilerSession); cdecl; +begin + try + IProfilerSessionImpl(this).dispose(); + except + on e: Exception do FbException.catchException(nil, e); + end +end; + +function IProfilerSessionImpl_getIdDispatcher(this: IProfilerSession): Int64; cdecl; +begin + try + Result := IProfilerSessionImpl(this).getId(); + except + on e: Exception do FbException.catchException(nil, e); + end +end; + +function IProfilerSessionImpl_getFlagsDispatcher(this: IProfilerSession): Cardinal; cdecl; +begin + try + Result := IProfilerSessionImpl(this).getFlags(); + except + on e: Exception do FbException.catchException(nil, e); + end +end; + +procedure IProfilerSessionImpl_finishDispatcher(this: IProfilerSession; status: IStatus; timestamp: ISC_TIMESTAMP_TZ); cdecl; +begin + try + IProfilerSessionImpl(this).finish(status, timestamp); + except + on e: Exception do FbException.catchException(status, e); + end +end; + +procedure IProfilerSessionImpl_defineStatementDispatcher(this: IProfilerSession; status: IStatus; statementId: Int64; parentStatementId: Int64; type_: PAnsiChar; packageName: PAnsiChar; routineName: PAnsiChar; sqlText: PAnsiChar); cdecl; +begin + try + IProfilerSessionImpl(this).defineStatement(status, statementId, parentStatementId, type_, packageName, routineName, sqlText); + except + on e: Exception do FbException.catchException(status, e); + end +end; + +procedure IProfilerSessionImpl_defineRecordSourceDispatcher(this: IProfilerSession; statementId: Int64; cursorId: Cardinal; recSourceId: Cardinal; accessPath: PAnsiChar; parentRecSourceId: Cardinal); cdecl; +begin + try + IProfilerSessionImpl(this).defineRecordSource(statementId, cursorId, recSourceId, accessPath, parentRecSourceId); + except + on e: Exception do FbException.catchException(nil, e); + end +end; + +procedure IProfilerSessionImpl_onRequestStartDispatcher(this: IProfilerSession; status: IStatus; requestId: Int64; statementId: Int64; callerRequestId: Int64; timestamp: ISC_TIMESTAMP_TZ); cdecl; +begin + try + IProfilerSessionImpl(this).onRequestStart(status, requestId, statementId, callerRequestId, timestamp); + except + on e: Exception do FbException.catchException(status, e); + end +end; + +procedure IProfilerSessionImpl_onRequestFinishDispatcher(this: IProfilerSession; status: IStatus; requestId: Int64; timestamp: ISC_TIMESTAMP_TZ); cdecl; +begin + try + IProfilerSessionImpl(this).onRequestFinish(status, requestId, timestamp); + except + on e: Exception do FbException.catchException(status, e); + end +end; + +procedure IProfilerSessionImpl_beforePsqlLineColumnDispatcher(this: IProfilerSession; requestId: Int64; line: Cardinal; column: Cardinal); cdecl; +begin + try + IProfilerSessionImpl(this).beforePsqlLineColumn(requestId, line, column); + except + on e: Exception do FbException.catchException(nil, e); + end +end; + +procedure IProfilerSessionImpl_afterPsqlLineColumnDispatcher(this: IProfilerSession; requestId: Int64; line: Cardinal; column: Cardinal; runTime: QWord); cdecl; +begin + try + IProfilerSessionImpl(this).afterPsqlLineColumn(requestId, line, column, runTime); + except + on e: Exception do FbException.catchException(nil, e); + end +end; + +procedure IProfilerSessionImpl_beforeRecordSourceOpenDispatcher(this: IProfilerSession; requestId: Int64; cursorId: Cardinal; recSourceId: Cardinal); cdecl; +begin + try + IProfilerSessionImpl(this).beforeRecordSourceOpen(requestId, cursorId, recSourceId); + except + on e: Exception do FbException.catchException(nil, e); + end +end; + +procedure IProfilerSessionImpl_afterRecordSourceOpenDispatcher(this: IProfilerSession; requestId: Int64; cursorId: Cardinal; recSourceId: Cardinal; runTime: QWord); cdecl; +begin + try + IProfilerSessionImpl(this).afterRecordSourceOpen(requestId, cursorId, recSourceId, runTime); + except + on e: Exception do FbException.catchException(nil, e); + end +end; + +procedure IProfilerSessionImpl_beforeRecordSourceGetRecordDispatcher(this: IProfilerSession; requestId: Int64; cursorId: Cardinal; recSourceId: Cardinal); cdecl; +begin + try + IProfilerSessionImpl(this).beforeRecordSourceGetRecord(requestId, cursorId, recSourceId); + except + on e: Exception do FbException.catchException(nil, e); + end +end; + +procedure IProfilerSessionImpl_afterRecordSourceGetRecordDispatcher(this: IProfilerSession; requestId: Int64; cursorId: Cardinal; recSourceId: Cardinal; runTime: QWord); cdecl; +begin + try + IProfilerSessionImpl(this).afterRecordSourceGetRecord(requestId, cursorId, recSourceId, runTime); + except + on e: Exception do FbException.catchException(nil, e); + end +end; + +var + IProfilerSessionImpl_vTable: ProfilerSessionVTable; + +constructor IProfilerSessionImpl.create; +begin + vTable := IProfilerSessionImpl_vTable; +end; + constructor FbException.create(status: IStatus); begin inherited Create('FbException'); @@ -16331,6 +16723,33 @@ initialization IReplicatedSessionImpl_vTable.cleanupTransaction := @IReplicatedSessionImpl_cleanupTransactionDispatcher; IReplicatedSessionImpl_vTable.setSequence := @IReplicatedSessionImpl_setSequenceDispatcher; + IProfilerPluginImpl_vTable := ProfilerPluginVTable.create; + IProfilerPluginImpl_vTable.version := 4; + IProfilerPluginImpl_vTable.addRef := @IProfilerPluginImpl_addRefDispatcher; + IProfilerPluginImpl_vTable.release := @IProfilerPluginImpl_releaseDispatcher; + IProfilerPluginImpl_vTable.setOwner := @IProfilerPluginImpl_setOwnerDispatcher; + IProfilerPluginImpl_vTable.getOwner := @IProfilerPluginImpl_getOwnerDispatcher; + IProfilerPluginImpl_vTable.init := @IProfilerPluginImpl_initDispatcher; + IProfilerPluginImpl_vTable.startSession := @IProfilerPluginImpl_startSessionDispatcher; + IProfilerPluginImpl_vTable.flush := @IProfilerPluginImpl_flushDispatcher; + + IProfilerSessionImpl_vTable := ProfilerSessionVTable.create; + IProfilerSessionImpl_vTable.version := 3; + IProfilerSessionImpl_vTable.dispose := @IProfilerSessionImpl_disposeDispatcher; + IProfilerSessionImpl_vTable.getId := @IProfilerSessionImpl_getIdDispatcher; + IProfilerSessionImpl_vTable.getFlags := @IProfilerSessionImpl_getFlagsDispatcher; + IProfilerSessionImpl_vTable.finish := @IProfilerSessionImpl_finishDispatcher; + IProfilerSessionImpl_vTable.defineStatement := @IProfilerSessionImpl_defineStatementDispatcher; + IProfilerSessionImpl_vTable.defineRecordSource := @IProfilerSessionImpl_defineRecordSourceDispatcher; + IProfilerSessionImpl_vTable.onRequestStart := @IProfilerSessionImpl_onRequestStartDispatcher; + IProfilerSessionImpl_vTable.onRequestFinish := @IProfilerSessionImpl_onRequestFinishDispatcher; + IProfilerSessionImpl_vTable.beforePsqlLineColumn := @IProfilerSessionImpl_beforePsqlLineColumnDispatcher; + IProfilerSessionImpl_vTable.afterPsqlLineColumn := @IProfilerSessionImpl_afterPsqlLineColumnDispatcher; + IProfilerSessionImpl_vTable.beforeRecordSourceOpen := @IProfilerSessionImpl_beforeRecordSourceOpenDispatcher; + IProfilerSessionImpl_vTable.afterRecordSourceOpen := @IProfilerSessionImpl_afterRecordSourceOpenDispatcher; + IProfilerSessionImpl_vTable.beforeRecordSourceGetRecord := @IProfilerSessionImpl_beforeRecordSourceGetRecordDispatcher; + IProfilerSessionImpl_vTable.afterRecordSourceGetRecord := @IProfilerSessionImpl_afterRecordSourceGetRecordDispatcher; + finalization IVersionedImpl_vTable.destroy; IReferenceCountedImpl_vTable.destroy; @@ -16427,5 +16846,7 @@ finalization IReplicatedRecordImpl_vTable.destroy; IReplicatedTransactionImpl_vTable.destroy; IReplicatedSessionImpl_vTable.destroy; + IProfilerPluginImpl_vTable.destroy; + IProfilerSessionImpl_vTable.destroy; end. diff --git a/src/jrd/Attachment.cpp b/src/jrd/Attachment.cpp index 2cfbfb0c62..2bea03537a 100644 --- a/src/jrd/Attachment.cpp +++ b/src/jrd/Attachment.cpp @@ -42,7 +42,7 @@ #include "../jrd/tpc_proto.h" #include "../jrd/extds/ExtDS.h" - +#include "../jrd/ProfilerManager.h" #include "../jrd/replication/Applier.h" #include "../jrd/replication/Manager.h" @@ -1150,3 +1150,16 @@ int Attachment::blockingAstReplSet(void* ast_object) return 0; } + +ProfilerManager* Attachment::getProfilerManager(thread_db* tdbb) +{ + auto profilerManager = att_profiler_manager.get(); + if (!profilerManager) + att_profiler_manager.reset(profilerManager = ProfilerManager::create(tdbb)); + return profilerManager; +} + +bool Attachment::isProfilerActive() +{ + return att_profiler_manager && att_profiler_manager->isActive(); +} diff --git a/src/jrd/Attachment.h b/src/jrd/Attachment.h index 1e42f2a263..900bbffad6 100644 --- a/src/jrd/Attachment.h +++ b/src/jrd/Attachment.h @@ -95,6 +95,7 @@ namespace Jrd class TrigVector; class Function; class JrdStatement; + class ProfilerManager; class Validation; class Applier; @@ -562,6 +563,7 @@ public: ULONG att_flags; // Flags describing the state of the attachment SSHORT att_client_charset; // user's charset specified in dpb SSHORT att_charset; // current (client or external) attachment charset + bool att_in_system_routine = false; // running a system routine Lock* att_long_locks; // outstanding two phased locks #ifdef DEBUG_LCK_LIST UCHAR att_long_locks_type; // Lock type of the first lock in list @@ -795,6 +797,9 @@ public: void checkReplSetLock(thread_db* tdbb); void invalidateReplSet(thread_db* tdbb, bool broadcast); + ProfilerManager* getProfilerManager(thread_db* tdbb); + bool isProfilerActive(); + JProvider* getProvider() { fb_assert(att_provider); @@ -814,6 +819,7 @@ private: Firebird::Array att_batches; InitialOptions att_initial_options; // Initial session options DebugOptions att_debug_options; + Firebird::AutoPtr att_profiler_manager; // ProfilerManager Lock* att_repl_lock; // Replication set lock JProvider* att_provider; // Provider which created this attachment diff --git a/src/jrd/ExtEngineManager.cpp b/src/jrd/ExtEngineManager.cpp index 93a0cefa01..57da7980ca 100644 --- a/src/jrd/ExtEngineManager.cpp +++ b/src/jrd/ExtEngineManager.cpp @@ -60,6 +60,9 @@ using namespace Firebird; using namespace Jrd; +static EngineCheckout::Type checkoutType(IExternalEngine* engine); + + namespace { // Internal message node. @@ -477,8 +480,6 @@ namespace } -namespace Jrd { - template class ExtEngineManager::ContextManager { public: @@ -556,7 +557,7 @@ private: char charSetName[MAX_SQL_IDENTIFIER_SIZE]; { // scope - EngineCheckout cout(tdbb, FB_FUNCTION); + EngineCheckout cout(tdbb, FB_FUNCTION, checkoutType(attInfo->engine)); FbLocalStatus status; obj->getCharSet(&status, attInfo->context, charSetName, MAX_SQL_IDENTIFIER_LEN); @@ -736,7 +737,7 @@ void ExtEngineManager::Function::execute(thread_db* tdbb, UCHAR* inMsg, UCHAR* o CallerName(obj_udf, udf->getName().identifier, userName) : CallerName(obj_package_header, udf->getName().package, userName))); - EngineCheckout cout(tdbb, FB_FUNCTION); + EngineCheckout cout(tdbb, FB_FUNCTION, checkoutType(attInfo->engine)); FbLocalStatus status; function->execute(&status, attInfo->context, inMsg, outMsg); @@ -792,7 +793,7 @@ ExtEngineManager::ResultSet::ResultSet(thread_db* tdbb, UCHAR* inMsg, UCHAR* out charSet = attachment->att_charset; - EngineCheckout cout(tdbb, FB_FUNCTION); + EngineCheckout cout(tdbb, FB_FUNCTION, checkoutType(attInfo->engine)); FbLocalStatus status; resultSet = procedure->procedure->open(&status, attInfo->context, inMsg, outMsg); @@ -804,7 +805,7 @@ ExtEngineManager::ResultSet::~ResultSet() { if (resultSet) { - EngineCheckout cout(JRD_get_thread_data(), FB_FUNCTION); + EngineCheckout cout(JRD_get_thread_data(), FB_FUNCTION, checkoutType(attInfo->engine)); resultSet->dispose(); } } @@ -824,7 +825,7 @@ bool ExtEngineManager::ResultSet::fetch(thread_db* tdbb) CallerName(obj_procedure, procedure->prc->getName().identifier, userName) : CallerName(obj_package_header, procedure->prc->getName().package, userName))); - EngineCheckout cout(tdbb, FB_FUNCTION); + EngineCheckout cout(tdbb, FB_FUNCTION, checkoutType(attInfo->engine)); FbLocalStatus status; bool ret = resultSet->fetch(&status); @@ -914,7 +915,7 @@ void ExtEngineManager::Trigger::execute(thread_db* tdbb, jrd_req* request, unsig setValues(tdbb, request, newMsg, newRpb); { // scope - EngineCheckout cout(tdbb, FB_FUNCTION); + EngineCheckout cout(tdbb, FB_FUNCTION, checkoutType(attInfo->engine)); FbLocalStatus status; trigger->execute(&status, attInfo->context, action, @@ -1232,7 +1233,7 @@ void ExtEngineManager::closeAttachment(thread_db* tdbb, Attachment* attachment) enginesCopy.put(accessor.current()->first, accessor.current()->second); } - EngineCheckout cout(tdbb, FB_FUNCTION, true); + EngineCheckout cout(tdbb, FB_FUNCTION, EngineCheckout::UNNECESSARY); EnginesMap::Accessor accessor(&enginesCopy); for (bool found = accessor.getFirst(); found; found = accessor.getNext()) @@ -1247,7 +1248,7 @@ void ExtEngineManager::closeAttachment(thread_db* tdbb, Attachment* attachment) FbLocalStatus status; engine->closeAttachment(&status, attInfo->context); //// FIXME: log status - // Check whether the engine is used by other attachments. + // Check whether the engine is used by other attachments. // If no one uses, release it. bool close = true; WriteLockGuard writeGuard(enginesLock, FB_FUNCTION); @@ -1310,7 +1311,7 @@ void ExtEngineManager::makeFunction(thread_db* tdbb, CompilerScratch* csb, Jrd:: RefPtr extInputParameters, extOutputParameters; { // scope - EngineCheckout cout(tdbb, FB_FUNCTION); + EngineCheckout cout(tdbb, FB_FUNCTION, checkoutType(attInfo->engine)); externalFunction = attInfo->engine->makeFunction(&status, attInfo->context, metadata, inBuilder, outBuilder); @@ -1393,7 +1394,7 @@ void ExtEngineManager::makeFunction(thread_db* tdbb, CompilerScratch* csb, Jrd:: } catch (...) { - EngineCheckout cout(tdbb, FB_FUNCTION); + EngineCheckout cout(tdbb, FB_FUNCTION, checkoutType(attInfo->engine)); externalFunction->dispose(); throw; } @@ -1435,7 +1436,7 @@ void ExtEngineManager::makeProcedure(thread_db* tdbb, CompilerScratch* csb, jrd_ RefPtr extInputParameters, extOutputParameters; { // scope - EngineCheckout cout(tdbb, FB_FUNCTION); + EngineCheckout cout(tdbb, FB_FUNCTION, checkoutType(attInfo->engine)); externalProcedure = attInfo->engine->makeProcedure(&status, attInfo->context, metadata, inBuilder, outBuilder); @@ -1525,7 +1526,7 @@ void ExtEngineManager::makeProcedure(thread_db* tdbb, CompilerScratch* csb, jrd_ } catch (...) { - EngineCheckout cout(tdbb, FB_FUNCTION); + EngineCheckout cout(tdbb, FB_FUNCTION, checkoutType(attInfo->engine)); externalProcedure->dispose(); throw; } @@ -1586,7 +1587,7 @@ void ExtEngineManager::makeTrigger(thread_db* tdbb, CompilerScratch* csb, Jrd::T IExternalTrigger* externalTrigger; { // scope - EngineCheckout cout(tdbb, FB_FUNCTION); + EngineCheckout cout(tdbb, FB_FUNCTION, checkoutType(attInfo->engine)); FbLocalStatus status; externalTrigger = attInfo->engine->makeTrigger(&status, attInfo->context, metadata, @@ -1624,7 +1625,7 @@ void ExtEngineManager::makeTrigger(thread_db* tdbb, CompilerScratch* csb, Jrd::T } catch (...) { - EngineCheckout cout(tdbb, FB_FUNCTION); + EngineCheckout cout(tdbb, FB_FUNCTION, checkoutType(attInfo->engine)); externalTrigger->dispose(); throw; } @@ -1733,7 +1734,7 @@ ExtEngineManager::EngineAttachmentInfo* ExtEngineManager::getEngineAttachment( enginesAttachments.put(key, attInfo); ContextManager ctxManager(tdbb, attInfo, attInfo->adminCharSet); - EngineCheckout cout(tdbb, FB_FUNCTION); + EngineCheckout cout(tdbb, FB_FUNCTION, checkoutType(attInfo->engine)); FbLocalStatus status; engine->openAttachment(&status, attInfo->context); //// FIXME: log status } @@ -1776,4 +1777,10 @@ void ExtEngineManager::setupAdminCharSet(thread_db* tdbb, IExternalEngine* engin } -} // namespace Jrd +//--------------------- + + +static EngineCheckout::Type checkoutType(IExternalEngine* engine) +{ + return engine == SystemEngine::INSTANCE ? EngineCheckout::AVOID : EngineCheckout::REQUIRED; +} diff --git a/src/jrd/GlobalRWLock.cpp b/src/jrd/GlobalRWLock.cpp index 3ab602a092..f7a975bbea 100644 --- a/src/jrd/GlobalRWLock.cpp +++ b/src/jrd/GlobalRWLock.cpp @@ -111,7 +111,7 @@ bool GlobalRWLock::lockWrite(thread_db* tdbb, SSHORT wait) while (readers > 0 ) { - EngineCheckout cout(tdbb, FB_FUNCTION, true); + EngineCheckout cout(tdbb, FB_FUNCTION, EngineCheckout::UNNECESSARY); noReaders.wait(counterMutex); } @@ -120,7 +120,7 @@ bool GlobalRWLock::lockWrite(thread_db* tdbb, SSHORT wait) while (currentWriter || pendingLock) { - EngineCheckout cout(tdbb, FB_FUNCTION, true); + EngineCheckout cout(tdbb, FB_FUNCTION, EngineCheckout::UNNECESSARY); writerFinished.wait(counterMutex); } @@ -237,7 +237,7 @@ bool GlobalRWLock::lockRead(thread_db* tdbb, SSHORT wait, const bool queueJump) while (pendingWriters > 0 || currentWriter) { - EngineCheckout cout(tdbb, FB_FUNCTION, true); + EngineCheckout cout(tdbb, FB_FUNCTION, EngineCheckout::UNNECESSARY); writerFinished.wait(counterMutex); } @@ -248,7 +248,7 @@ bool GlobalRWLock::lockRead(thread_db* tdbb, SSHORT wait, const bool queueJump) break; MutexUnlockGuard cout(counterMutex, FB_FUNCTION); - EngineCheckout cout2(tdbb, FB_FUNCTION, true); + EngineCheckout cout2(tdbb, FB_FUNCTION, EngineCheckout::UNNECESSARY); Thread::yield(); } diff --git a/src/jrd/JrdStatement.cpp b/src/jrd/JrdStatement.cpp index 1ce4864d13..76cf2f3fbb 100644 --- a/src/jrd/JrdStatement.cpp +++ b/src/jrd/JrdStatement.cpp @@ -648,10 +648,7 @@ void JrdStatement::release(thread_db* tdbb) const auto attachment = tdbb->getAttachment(); - FB_SIZE_T pos; - if (attachment->att_statements.find(this, pos)) - attachment->att_statements.remove(pos); - else + if (!attachment->att_statements.findAndRemove(this)) fb_assert(false); sqlText = NULL; diff --git a/src/jrd/ProfilerManager.cpp b/src/jrd/ProfilerManager.cpp new file mode 100644 index 0000000000..cca4c4ba39 --- /dev/null +++ b/src/jrd/ProfilerManager.cpp @@ -0,0 +1,526 @@ +/* + * The contents of this file are subject to the Initial + * Developer's Public License Version 1.0 (the "License"); + * you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * http://www.ibphoenix.com/main.nfs?a=ibphoenix&page=ibp_idpl. + * + * Software distributed under the License is distributed AS IS, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. + * See the License for the specific language governing rights + * and limitations under the License. + * + * The Original Code was created by Adriano dos Santos Fernandes + * for the Firebird Open Source RDBMS project. + * + * Copyright (c) 2020 Adriano dos Santos Fernandes + * and all contributors signed below. + * + * All Rights Reserved. + * Contributor(s): ______________________________________. + */ + +#include "firebird.h" +#include "../jrd/ProfilerManager.h" +#include "../jrd/Record.h" +#include "../jrd/ini.h" +#include "../jrd/tra.h" +#include "../jrd/ids.h" +#include "../jrd/recsrc/Cursor.h" +#include "../jrd/dpm_proto.h" +#include "../jrd/met_proto.h" + +using namespace Jrd; +using namespace Firebird; + + +//-------------------------------------- + + +IExternalResultSet* ProfilerPackage::flushProcedure(ThrowStatusExceptionWrapper* /*status*/, + IExternalContext* context, const void* in, void* out) +{ + const auto tdbb = JRD_get_thread_data(); + const auto attachment = tdbb->getAttachment(); + const auto transaction = tdbb->getTransaction(); + + const auto profilerManager = attachment->getProfilerManager(tdbb); + AutoSetRestore pauseProfiler(&profilerManager->paused, true); + + profilerManager->flush(transaction->getInterface(true)); + + return nullptr; +} + +IExternalResultSet* ProfilerPackage::finishSessionProcedure(ThrowStatusExceptionWrapper* /*status*/, + IExternalContext* context, const FinishSessionInput::Type* in, void* out) +{ + const auto tdbb = JRD_get_thread_data(); + const auto attachment = tdbb->getAttachment(); + const auto transaction = tdbb->getTransaction(); + + const auto profilerManager = attachment->getProfilerManager(tdbb); + AutoSetRestore pauseProfiler(&profilerManager->paused, true); + + if (profilerManager->currentSession) + { + const auto timestamp = TimeZoneUtil::getCurrentTimeStamp(attachment->att_current_timezone); + LogLocalStatus status("Profiler finish"); + + profilerManager->currentSession->pluginSession->finish(&status, timestamp); + profilerManager->currentSession = nullptr; + } + + if (in->flush) + profilerManager->flush(transaction->getInterface(true)); + + return nullptr; +} + +IExternalResultSet* ProfilerPackage::pauseSessionProcedure(ThrowStatusExceptionWrapper* /*status*/, + IExternalContext* context, const PauseSessionInput::Type* in, void* out) +{ + const auto tdbb = JRD_get_thread_data(); + const auto attachment = tdbb->getAttachment(); + const auto transaction = tdbb->getTransaction(); + + const auto profilerManager = attachment->getProfilerManager(tdbb); + + if (!profilerManager->currentSession) + return nullptr; + + profilerManager->paused = true; + + if (in->flush) + profilerManager->flush(transaction->getInterface(true)); + + return nullptr; +} + +IExternalResultSet* ProfilerPackage::resumeSessionProcedure(ThrowStatusExceptionWrapper* /*status*/, + IExternalContext* context, const void* in, void* out) +{ + const auto tdbb = JRD_get_thread_data(); + const auto attachment = tdbb->getAttachment(); + + const auto profilerManager = attachment->getProfilerManager(tdbb); + + if (profilerManager->currentSession && profilerManager->paused) + profilerManager->paused = false; + + return nullptr; +} + +void ProfilerPackage::startSessionFunction(ThrowStatusExceptionWrapper* /*status*/, + IExternalContext* context, const StartSessionInput::Type* in, StartSessionOutput::Type* out) +{ + const auto tdbb = JRD_get_thread_data(); + const auto attachment = tdbb->getAttachment(); + const auto transaction = tdbb->getTransaction(); + + const PathName pluginName(in->pluginName.str, in->pluginName.length); + const string description(in->description.str, in->descriptionNull ? 0 : in->description.length); + + const auto profilerManager = attachment->getProfilerManager(tdbb); + AutoSetRestore pauseProfiler(&profilerManager->paused, true); + + out->sessionIdNull = FB_FALSE; + out->sessionId = profilerManager->startSession(tdbb, pluginName, description); +} + + +//-------------------------------------- + + +ProfilerManager::ProfilerManager(thread_db* tdbb) + : activePlugins(*tdbb->getAttachment()->att_pool) +{ +} + +ProfilerManager* ProfilerManager::create(thread_db* tdbb) +{ + return FB_NEW_POOL(*tdbb->getAttachment()->att_pool) ProfilerManager(tdbb); +} + +SINT64 ProfilerManager::startSession(thread_db* tdbb, const PathName& pluginName, const string& description) +{ + const auto attachment = tdbb->getAttachment(); + const auto transaction = tdbb->getTransaction(); + ThrowLocalStatus status; + + const auto timestamp = TimeZoneUtil::getCurrentTimeStamp(attachment->att_current_timezone); + + if (currentSession) + { + currentSession->pluginSession->finish(&status, timestamp); + currentSession = nullptr; + } + + auto pluginPtr = activePlugins.get(pluginName); + + AutoPlugin plugin; + + if (pluginPtr) + { + (*pluginPtr)->addRef(); + plugin.reset(pluginPtr->get()); + } + else + { + GetPlugins plugins(IPluginManager::TYPE_PROFILER, pluginName.c_str()); + + if (!plugins.hasData()) + { + string msg; + msg.printf("Profiler plugin %s is not found", pluginName.c_str()); + (Arg::Gds(isc_random) << msg).raise(); + } + + plugin.reset(plugins.plugin()); + plugin->addRef(); + + plugin->init(&status, attachment->getInterface(), transaction->getInterface(true)); + + plugin->addRef(); + activePlugins.put(pluginName)->reset(plugin.get()); + } + + AutoDispose pluginSession = plugin->startSession(&status, + transaction->getInterface(true), + description.c_str(), + timestamp); + + auto& pool = *tdbb->getAttachment()->att_pool; + + currentSession.reset(FB_NEW_POOL(pool) ProfilerManager::Session(pool)); + currentSession->pluginSession = std::move(pluginSession); + currentSession->plugin = std::move(plugin); + currentSession->flags = currentSession->pluginSession->getFlags(); + + paused = false; + + return currentSession->pluginSession->getId(); +} + +void ProfilerManager::prepareRecSource(thread_db* tdbb, jrd_req* request, const RecordSource* rsb) +{ + auto profileStatement = getStatement(request); + + if (!profileStatement) + return; + + if (profileStatement->recSourceSequence.exist(rsb->getRecSourceProfileId())) + return; + + Array> tree; + tree.add({rsb, nullptr}); + + for (unsigned pos = 0; pos < tree.getCount(); ++pos) + { + const auto thisRsb = tree[pos].first; + + Array children; + thisRsb->getChildren(children); + + unsigned childPos = pos; + + for (const auto child : children) + tree.insert(++childPos, {child, thisRsb}); + } + + NonPooledMap idSequenceMap; + auto sequencePtr = profileStatement->cursorNextSequence.getOrPut(rsb->getCursorProfileId()); + + for (const auto& pair : tree) + { + const auto cursorId = pair.first->getCursorProfileId(); + const auto recSourceId = pair.first->getRecSourceProfileId(); + idSequenceMap.put(recSourceId, ++*sequencePtr); + + string accessPath; + pair.first->print(tdbb, accessPath, true, 0, false); + + constexpr auto INDENT_MARKER = "\n "; + + if (accessPath.find(INDENT_MARKER) == 0) + { + unsigned pos = 0; + + do { + accessPath.erase(pos + 1, 4); + } while ((pos = accessPath.find(INDENT_MARKER, pos + 1)) != string::npos); + } + + ULONG parentSequence = 0; + + if (pair.second) + parentSequence = *idSequenceMap.get(pair.second->getRecSourceProfileId()); + + currentSession->pluginSession->defineRecordSource(profileStatement->id, cursorId, + *sequencePtr, accessPath.c_str(), parentSequence); + + profileStatement->recSourceSequence.put(recSourceId, *sequencePtr); + } +} + +void ProfilerManager::onRequestFinish(jrd_req* request) +{ + if (const auto profileRequestId = getRequest(request, 0)) + { + const auto timestamp = TimeZoneUtil::getCurrentTimeStamp(request->req_attachment->att_current_timezone); + + LogLocalStatus status("Profiler onRequestFinish"); + currentSession->pluginSession->onRequestFinish(&status, profileRequestId, timestamp); + + currentSession->requests.findAndRemove(profileRequestId); + } +} + +void ProfilerManager::beforePsqlLineColumn(jrd_req* request, ULONG line, ULONG column) +{ + if (const auto profileRequestId = getRequest(request, IProfilerSession::FLAG_BEFORE_EVENTS)) + currentSession->pluginSession->beforePsqlLineColumn(profileRequestId, line, column); +} + +void ProfilerManager::afterPsqlLineColumn(jrd_req* request, ULONG line, ULONG column, FB_UINT64 runTime) +{ + if (const auto profileRequestId = getRequest(request, IProfilerSession::FLAG_AFTER_EVENTS)) + currentSession->pluginSession->afterPsqlLineColumn(profileRequestId, line, column, runTime); +} + +void ProfilerManager::beforeRecordSourceOpen(jrd_req* request, const RecordSource* rsb) +{ + if (const auto profileRequestId = getRequest(request, IProfilerSession::FLAG_BEFORE_EVENTS)) + { + const auto profileStatement = getStatement(request); + const auto sequencePtr = profileStatement->recSourceSequence.get(rsb->getRecSourceProfileId()); + fb_assert(sequencePtr); + + currentSession->pluginSession->beforeRecordSourceOpen( + profileRequestId, rsb->getCursorProfileId(), *sequencePtr); + } +} + +void ProfilerManager::afterRecordSourceOpen(jrd_req* request, const RecordSource* rsb, FB_UINT64 runTime) +{ + if (const auto profileRequestId = getRequest(request, IProfilerSession::FLAG_AFTER_EVENTS)) + { + const auto profileStatement = getStatement(request); + const auto sequencePtr = profileStatement->recSourceSequence.get(rsb->getRecSourceProfileId()); + fb_assert(sequencePtr); + + currentSession->pluginSession->afterRecordSourceOpen( + profileRequestId, rsb->getCursorProfileId(), *sequencePtr, runTime); + } +} + +void ProfilerManager::beforeRecordSourceGetRecord(jrd_req* request, const RecordSource* rsb) +{ + if (const auto profileRequestId = getRequest(request, IProfilerSession::FLAG_BEFORE_EVENTS)) + { + const auto profileStatement = getStatement(request); + const auto sequencePtr = profileStatement->recSourceSequence.get(rsb->getRecSourceProfileId()); + fb_assert(sequencePtr); + + currentSession->pluginSession->beforeRecordSourceGetRecord( + profileRequestId, rsb->getCursorProfileId(), *sequencePtr); + } +} + +void ProfilerManager::afterRecordSourceGetRecord(jrd_req* request, const RecordSource* rsb, FB_UINT64 runTime) +{ + if (const auto profileRequestId = getRequest(request, IProfilerSession::FLAG_AFTER_EVENTS)) + { + const auto profileStatement = getStatement(request); + const auto sequencePtr = profileStatement->recSourceSequence.get(rsb->getRecSourceProfileId()); + fb_assert(sequencePtr); + + currentSession->pluginSession->afterRecordSourceGetRecord( + profileRequestId, rsb->getCursorProfileId(), *sequencePtr, runTime); + } +} + +void ProfilerManager::flush(ITransaction* transaction) +{ + auto pluginAccessor = activePlugins.accessor(); + + for (bool hasNext = pluginAccessor.getFirst(); hasNext;) + { + auto& pluginName = pluginAccessor.current()->first; + auto& plugin = pluginAccessor.current()->second; + + LogLocalStatus status("Profiler flush"); + plugin->flush(&status, transaction); + + hasNext = pluginAccessor.getNext(); + + if (!currentSession || plugin.get() != currentSession->plugin.get()) + activePlugins.remove(pluginName); + } +} + +ProfilerManager::Statement* ProfilerManager::getStatement(jrd_req* request) +{ + if (!isActive()) + return nullptr; + + auto mainProfileStatement = currentSession->statements.get(request->getStatement()->getStatementId()); + + if (mainProfileStatement) + return mainProfileStatement; + + for (const auto* statement = request->getStatement(); + statement && !currentSession->statements.exist(statement->getStatementId()); + statement = statement->parentStatement) + { + MetaName packageName; + MetaName routineName; + const char* type; + + if (const auto routine = statement->getRoutine()) + { + if (statement->procedure) + type = "PROCEDURE"; + else if (statement->function) + type = "FUNCTION"; + + packageName = routine->getName().package; + routineName = routine->getName().identifier; + } + else if (statement->triggerName.hasData()) + { + type = "TRIGGER"; + routineName = statement->triggerName; + } + else + type = "BLOCK"; + + const StmtNumber parentStatementId = statement->parentStatement ? + statement->parentStatement->getStatementId() : 0; + + LogLocalStatus status("Profiler defineStatement"); + currentSession->pluginSession->defineStatement(&status, + (SINT64) statement->getStatementId(), (SINT64) parentStatementId, + type, packageName.nullStr(), routineName.nullStr(), + (statement->sqlText.hasData() ? statement->sqlText->c_str() : "")); + + auto profileStatement = currentSession->statements.put(statement->getStatementId()); + profileStatement->id = statement->getStatementId(); + + if (!mainProfileStatement) + mainProfileStatement = profileStatement; + } + + return mainProfileStatement; +} + +SINT64 ProfilerManager::getRequest(jrd_req* request, unsigned flags) +{ + if (!isActive() || (flags && !(currentSession->flags & flags))) + return 0; + + const auto mainRequestId = request->getRequestId(); + + if (!currentSession->requests.exist(mainRequestId)) + { + const auto timestamp = TimeZoneUtil::getCurrentTimeStamp(request->req_attachment->att_current_timezone); + + do + { + getStatement(request); // define the statement and ignore the result + + const StmtNumber callerRequestId = request->req_caller ? request->req_caller->getRequestId() : 0; + + LogLocalStatus status("Profiler onRequestStart"); + currentSession->pluginSession->onRequestStart(&status, + (SINT64) request->getRequestId(), (SINT64) request->getStatement()->getStatementId(), + (SINT64) callerRequestId, timestamp); + + currentSession->requests.add(request->getRequestId()); + + request = request->req_caller; + } while (request && !currentSession->requests.exist(request->getRequestId())); + } + + return mainRequestId; +} + + +//-------------------------------------- + + +ProfilerPackage::ProfilerPackage(MemoryPool& pool) + : SystemPackage( + pool, + "RDB$PROFILER", + ODS_13_1, + // procedures + { + SystemProcedure( + pool, + "FINISH_SESSION", + SystemProcedureFactory(), + prc_executable, + // input parameters + { + {"FLUSH", fld_bool, false} + }, + // output parameters + { + } + ), + SystemProcedure( + pool, + "FLUSH", + SystemProcedureFactory(), + prc_executable, + // input parameters + { + }, + // output parameters + { + } + ), + SystemProcedure( + pool, + "PAUSE_SESSION", + SystemProcedureFactory(), + prc_executable, + // input parameters + { + {"FLUSH", fld_bool, false} + }, + // output parameters + { + } + ), + SystemProcedure( + pool, + "RESUME_SESSION", + SystemProcedureFactory(), + prc_executable, + // input parameters + { + }, + // output parameters + { + } + ) + }, + // functions + { + SystemFunction( + pool, + "START_SESSION", + SystemFunctionFactory(), + // parameters + { + {"PLUGIN_NAME", fld_file_name2, true}, + {"DESCRIPTION", fld_short_description, true} + }, + {fld_prof_ses_id, false} + ) + } + ) +{ +} diff --git a/src/jrd/ProfilerManager.h b/src/jrd/ProfilerManager.h new file mode 100644 index 0000000000..eb9e9b92f6 --- /dev/null +++ b/src/jrd/ProfilerManager.h @@ -0,0 +1,172 @@ +/* + * The contents of this file are subject to the Initial + * Developer's Public License Version 1.0 (the "License"); + * you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * http://www.ibphoenix.com/main.nfs?a=ibphoenix&page=ibp_idpl. + * + * Software distributed under the License is distributed AS IS, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. + * See the License for the specific language governing rights + * and limitations under the License. + * + * The Original Code was created by Adriano dos Santos Fernandes + * for the Firebird Open Source RDBMS project. + * + * Copyright (c) 2020 Adriano dos Santos Fernandes + * and all contributors signed below. + * + * All Rights Reserved. + * Contributor(s): ______________________________________. + */ + +#ifndef JRD_PROFILER_MANAGER_H +#define JRD_PROFILER_MANAGER_H + +#include "firebird.h" +#include "firebird/Message.h" +#include "../common/classes/auto.h" +#include "../common/classes/fb_string.h" +#include "../common/classes/Nullable.h" +#include "../jrd/Monitoring.h" +#include "../jrd/SystemPackages.h" + +namespace Jrd { + +class Attachment; +class jrd_req; +class thread_db; +class Profiler; + + +class ProfilerManager final +{ + friend class ProfilerPackage; + +private: + class Statement final + { + public: + Statement(MemoryPool& pool) + : cursorNextSequence(pool), + recSourceSequence(pool) + { + } + + Statement(const Statement&) = delete; + void operator=(const Statement&) = delete; + + SINT64 id = 0; + Firebird::NonPooledMap cursorNextSequence; + Firebird::NonPooledMap recSourceSequence; + }; + + class Session final + { + public: + Session(MemoryPool& pool) + : statements(pool), + requests(pool) + { + } + + Session(const Session&) = delete; + void operator=(const Session&) = delete; + + Firebird::AutoPlugin plugin; + Firebird::AutoDispose pluginSession; + Firebird::RightPooledMap statements; + Firebird::SortedArray requests; + unsigned flags = 0; + }; + +private: + ProfilerManager(thread_db* tdbb); + +public: + static ProfilerManager* create(thread_db* tdbb); + + ProfilerManager(const ProfilerManager&) = delete; + void operator=(const ProfilerManager&) = delete; + +public: + SINT64 startSession(thread_db* tdbb, const Firebird::PathName& pluginName, const Firebird::string& description); + void prepareRecSource(thread_db* tdbb, jrd_req* request, const RecordSource* rsb); + void onRequestFinish(jrd_req* request); + void beforePsqlLineColumn(jrd_req* request, ULONG line, ULONG column); + void afterPsqlLineColumn(jrd_req* request, ULONG line, ULONG column, FB_UINT64 runTime); + void beforeRecordSourceOpen(jrd_req* request, const RecordSource* rsb); + void afterRecordSourceOpen(jrd_req* request, const RecordSource* rsb, FB_UINT64 runTime); + void beforeRecordSourceGetRecord(jrd_req* request, const RecordSource* rsb); + void afterRecordSourceGetRecord(jrd_req* request, const RecordSource* rsb, FB_UINT64 runTime); + + bool isActive() const + { + return currentSession && !paused; + } + +private: + void flush(Firebird::ITransaction* transaction); + Statement* getStatement(jrd_req* request); + SINT64 getRequest(jrd_req* request, unsigned flags); + +private: + Firebird::LeftPooledMap> activePlugins; + Firebird::AutoPtr currentSession; + bool paused = false; +}; + + +class ProfilerPackage : public SystemPackage +{ +public: + ProfilerPackage(Firebird::MemoryPool& pool); + +private: + static Firebird::IExternalResultSet* flushProcedure(Firebird::ThrowStatusExceptionWrapper* status, + Firebird::IExternalContext* context, const void* in, void* out); + + //---------- + + FB_MESSAGE(FinishSessionInput, Firebird::ThrowStatusExceptionWrapper, + (FB_BOOLEAN, flush) + ); + + static Firebird::IExternalResultSet* finishSessionProcedure(Firebird::ThrowStatusExceptionWrapper* status, + Firebird::IExternalContext* context, const FinishSessionInput::Type* in, void* out); + + //---------- + + FB_MESSAGE(PauseSessionInput, Firebird::ThrowStatusExceptionWrapper, + (FB_BOOLEAN, flush) + ); + + static Firebird::IExternalResultSet* pauseSessionProcedure(Firebird::ThrowStatusExceptionWrapper* status, + Firebird::IExternalContext* context, const PauseSessionInput::Type* in, void* out); + + //---------- + + static Firebird::IExternalResultSet* resumeSessionProcedure(Firebird::ThrowStatusExceptionWrapper* status, + Firebird::IExternalContext* context, const void* in, void* out); + + //---------- + + FB_MESSAGE(StartSessionInput, Firebird::ThrowStatusExceptionWrapper, + (FB_INTL_VARCHAR(255, CS_METADATA), pluginName) + //// TODO: Options: PSQL, SQL. + (FB_INTL_VARCHAR(255, CS_METADATA), description) + //// TODO: Plugin options. + ); + + FB_MESSAGE(StartSessionOutput, Firebird::ThrowStatusExceptionWrapper, + (FB_BIGINT, sessionId) + ); + + static void startSessionFunction(Firebird::ThrowStatusExceptionWrapper* status, + Firebird::IExternalContext* context, const StartSessionInput::Type* in, StartSessionOutput::Type* out); +}; + + +} // namespace + +#endif // JRD_PROFILER_MANAGER_H diff --git a/src/jrd/SystemPackages.cpp b/src/jrd/SystemPackages.cpp index dc618b1fb3..a6bf1ee627 100644 --- a/src/jrd/SystemPackages.cpp +++ b/src/jrd/SystemPackages.cpp @@ -23,6 +23,7 @@ #include "firebird.h" #include "../jrd/SystemPackages.h" #include "../jrd/TimeZone.h" +#include "../jrd/ProfilerManager.h" using namespace Firebird; using namespace Jrd; @@ -36,6 +37,7 @@ namespace : list(FB_NEW_POOL(pool) ObjectsArray(pool)) { list->add(TimeZonePackage(pool)); + list->add(ProfilerPackage(pool)); } static InitInstance INSTANCE; diff --git a/src/jrd/SystemPackages.h b/src/jrd/SystemPackages.h index 3fbfcd5924..72b42468ac 100644 --- a/src/jrd/SystemPackages.h +++ b/src/jrd/SystemPackages.h @@ -30,6 +30,7 @@ #include "../common/classes/objects_array.h" #include "../jrd/constants.h" #include "../jrd/ini.h" +#include "../jrd/jrd.h" #include "firebird/Interface.h" #include #include @@ -190,6 +191,41 @@ namespace Jrd > struct SystemProcedureFactory { + class SystemResultSet : + public + Firebird::DisposeIface< + Firebird::IExternalResultSetImpl< + SystemResultSet, + Firebird::ThrowStatusExceptionWrapper + > + > + { + public: + SystemResultSet(Attachment* aAttachment, Firebird::IExternalResultSet* aResultSet) + : attachment(aAttachment), + resultSet(aResultSet) + { + } + + public: + void dispose() override + { + delete this; + } + + public: + FB_BOOLEAN fetch(Firebird::ThrowStatusExceptionWrapper* status) override + { + Firebird::AutoSetRestore autoInSystemPackage(&attachment->att_in_system_routine, true); + + return resultSet->fetch(status); + } + + private: + Attachment* attachment; + Firebird::AutoDispose resultSet; + }; + class SystemProcedureImpl : public Firebird::DisposeIface< @@ -203,6 +239,9 @@ namespace Jrd SystemProcedureImpl(Firebird::ThrowStatusExceptionWrapper* status, Firebird::IMetadataBuilder* inBuilder, Firebird::IMetadataBuilder* outBuilder) { + const auto tdbb = JRD_get_thread_data(); + attachment = tdbb->getAttachment(); + Input::setup(status, inBuilder); Output::setup(status, outBuilder); } @@ -223,10 +262,17 @@ namespace Jrd Firebird::IExternalResultSet* open(Firebird::ThrowStatusExceptionWrapper* status, Firebird::IExternalContext* context, void* inMsg, void* outMsg) override { - return OpenFunction(status, context, + Firebird::AutoSetRestore autoInSystemPackage(&attachment->att_in_system_routine, true); + + const auto resultSet = OpenFunction(status, context, static_cast(inMsg), static_cast(outMsg)); + + return resultSet ? FB_NEW SystemResultSet(attachment, resultSet) : nullptr; } + + private: + Attachment* attachment; }; SystemProcedureImpl* operator()( @@ -265,6 +311,9 @@ namespace Jrd SystemFunctionImpl(Firebird::ThrowStatusExceptionWrapper* status, Firebird::IMetadataBuilder* inBuilder, Firebird::IMetadataBuilder* outBuilder) { + const auto tdbb = JRD_get_thread_data(); + attachment = tdbb->getAttachment(); + Input::setup(status, inBuilder); Output::setup(status, outBuilder); } @@ -279,10 +328,15 @@ namespace Jrd void execute(Firebird::ThrowStatusExceptionWrapper* status, Firebird::IExternalContext* context, void* inMsg, void* outMsg) override { + Firebird::AutoSetRestore autoInSystemPackage(&attachment->att_in_system_routine, true); + ExecFunction(status, context, static_cast(inMsg), static_cast(outMsg)); } + + private: + Attachment* attachment; }; SystemFunctionImpl* operator()( diff --git a/src/jrd/cmp.cpp b/src/jrd/cmp.cpp index c891e8a54f..c931926236 100644 --- a/src/jrd/cmp.cpp +++ b/src/jrd/cmp.cpp @@ -515,6 +515,8 @@ RecordSource* CMP_post_rse(thread_db* tdbb, CompilerScratch* csb, RseNode* rse) DEV_BLKCHK(csb, type_csb); DEV_BLKCHK(rse, type_nod); + AutoSetRestore autoCurrentCursorProfileId(&csb->csb_currentCursorProfileId, csb->csb_nextCursorProfileId++); + RecordSource* rsb = OPT_compile(tdbb, csb, rse, NULL); if (rse->flags & RseNode::FLAG_SINGULAR) diff --git a/src/jrd/exe.cpp b/src/jrd/exe.cpp index 36fd215fbe..1b431a0c5e 100644 --- a/src/jrd/exe.cpp +++ b/src/jrd/exe.cpp @@ -111,6 +111,7 @@ #include "../jrd/recsrc/RecordSource.h" #include "../jrd/recsrc/Cursor.h" #include "../jrd/Function.h" +#include "../jrd/ProfilerManager.h" using namespace Jrd; @@ -998,6 +999,11 @@ void EXE_unwind(thread_db* tdbb, jrd_req* request) } release_blobs(tdbb, request); + + const auto attachment = request->req_attachment; + + if (attachment->isProfilerActive() && !request->hasInternalStatement()) + attachment->getProfilerManager(tdbb)->onRequestFinish(request); } request->req_sorts.unlinkAll(); @@ -1336,10 +1342,10 @@ const StmtNode* EXE_looper(thread_db* tdbb, jrd_req* request, const StmtNode* no ERR_post(Arg::Gds(isc_req_no_trans)); SET_TDBB(tdbb); - Database* dbb = tdbb->getDatabase(); + const auto dbb = tdbb->getDatabase(); + const auto attachment = tdbb->getAttachment(); - // ASF: It's already a StmtNode, so do not do a virtual call in execution. - if (!node) /// if (!node || node->getKind() != DmlNode::KIND_STATEMENT + if (!node) BUGCHECK(147); // Save the old pool and request to restore on exit @@ -1353,6 +1359,11 @@ const StmtNode* EXE_looper(thread_db* tdbb, jrd_req* request, const StmtNode* no // Execute stuff until we drop + SINT64 lastPerfCounter = fb_utils::query_performance_counter(); + const StmtNode* profileNode = nullptr; + ULONG lastProfiledLine = node->line; + ULONG lastProfiledColumn = node->column; + while (node && !(request->req_flags & req_stall)) { try @@ -1366,6 +1377,33 @@ const StmtNode* EXE_looper(thread_db* tdbb, jrd_req* request, const StmtNode* no request->req_src_line = node->line; request->req_src_column = node->column; } + + if (attachment->isProfilerActive() && !request->hasInternalStatement()) + { + if (profileNode && + profileNode->hasLineColumn && + profileNode->isProfileAware() && + (profileNode->line != lastProfiledLine || profileNode->column != lastProfiledColumn)) + { + const SINT64 currentPerfCounter = fb_utils::query_performance_counter(); + + attachment->getProfilerManager(tdbb)->afterPsqlLineColumn(request, + profileNode->line, profileNode->column, + currentPerfCounter - lastPerfCounter); + + lastPerfCounter = currentPerfCounter; + lastProfiledLine = profileNode->line; + lastProfiledColumn = profileNode->column; + } + + if (node->hasLineColumn) + { + profileNode = node; + + attachment->getProfilerManager(tdbb)->beforePsqlLineColumn(request, + profileNode->line, profileNode->column); + } + } } node = node->execute(tdbb, request, &exeState); @@ -1410,6 +1448,20 @@ const StmtNode* EXE_looper(thread_db* tdbb, jrd_req* request, const StmtNode* no } } // while() + if (attachment->isProfilerActive() && + !request->hasInternalStatement() && + profileNode && + profileNode->hasLineColumn && + profileNode->isProfileAware() && + (profileNode->line != lastProfiledLine || profileNode->column != lastProfiledColumn)) + { + const SINT64 currentPerfCounter = fb_utils::query_performance_counter(); + + attachment->getProfilerManager(tdbb)->afterPsqlLineColumn(request, + profileNode->line, profileNode->column, + currentPerfCounter - lastPerfCounter); + } + request->adjustCallerStats(); fb_assert(request->req_auto_trans.getCount() == 0); diff --git a/src/jrd/exe.h b/src/jrd/exe.h index 0d04522a4d..44bf8f3c0a 100644 --- a/src/jrd/exe.h +++ b/src/jrd/exe.h @@ -549,6 +549,10 @@ public: ExprNode* csb_currentAssignTarget; dsc* csb_preferredDesc; // expected by receiving side data format + ULONG csb_currentCursorProfileId = 0; + ULONG csb_nextCursorProfileId = 1; + ULONG csb_nextRecSourceProfileId = 1; + struct csb_repeat { // We must zero-initialize this one diff --git a/src/jrd/fields.h b/src/jrd/fields.h index 6ffdaf9d6a..4ed5d2fbf2 100644 --- a/src/jrd/fields.h +++ b/src/jrd/fields.h @@ -223,3 +223,6 @@ FIELD(fld_keyword_name , nam_keyword_name , dtype_varying , METADATA_IDENTIFIER_CHAR_LEN, dsc_text_type_ascii , NULL , false) FIELD(fld_keyword_reserved, nam_keyword_reserved, dtype_boolean, 1 , 0 , NULL , false) + + FIELD(fld_short_description, nam_short_description, dtype_varying, 255 * METADATA_BYTES_PER_CHAR, dsc_text_type_metadata, NULL , true) + FIELD(fld_prof_ses_id , nam_prof_ses_id , dtype_int64 , sizeof(SINT64) , 0 , NULL , true) diff --git a/src/jrd/jrd.cpp b/src/jrd/jrd.cpp index dab848ce40..2f19aef4c7 100644 --- a/src/jrd/jrd.cpp +++ b/src/jrd/jrd.cpp @@ -944,7 +944,7 @@ void Trigger::compile(thread_db* tdbb) statement->triggerInvoker = att->getUserId(owner); if (sysTrigger) - statement->flags |= JrdStatement::FLAG_SYS_TRIGGER; + statement->flags |= JrdStatement::FLAG_SYS_TRIGGER | JrdStatement::FLAG_INTERNAL; if (flags & TRG_ignore_perm) statement->flags |= JrdStatement::FLAG_IGNORE_PERM; @@ -5136,7 +5136,7 @@ ITransaction* JAttachment::execute(CheckStatusWrapper* user_status, ITransaction DSQL_execute_immediate(tdbb, getHandle(), &tra, length, string, dialect, inMetadata, static_cast(inBuffer), outMetadata, static_cast(outBuffer), - false); + getHandle()->att_in_system_routine); jt = checkTranIntf(getStable(), jt, tra); } @@ -5548,7 +5548,7 @@ JStatement* JAttachment::prepare(CheckStatusWrapper* user_status, ITransaction* StatementMetadata::buildInfoItems(items, flags); statement = DSQL_prepare(tdbb, getHandle(), tra, stmtLength, sqlStmt, dialect, flags, - &items, &buffer, false); + &items, &buffer, getHandle()->att_in_system_routine); rc = FB_NEW JStatement(statement, getStable(), buffer); rc->addRef(); diff --git a/src/jrd/jrd.h b/src/jrd/jrd.h index 66c7e5206f..a69b7e52e9 100644 --- a/src/jrd/jrd.h +++ b/src/jrd/jrd.h @@ -1078,18 +1078,28 @@ namespace Jrd { class EngineCheckout { public: - EngineCheckout(thread_db* tdbb, const char* from, bool optional = false) + enum Type + { + REQUIRED, + UNNECESSARY, + AVOID + }; + + EngineCheckout(thread_db* tdbb, const char* from, Type type = REQUIRED) : m_tdbb(tdbb), m_from(from) { - Attachment* const att = tdbb ? tdbb->getAttachment() : NULL; + if (type != AVOID) + { + Attachment* const att = tdbb ? tdbb->getAttachment() : NULL; - if (att) - m_ref = att->getStable(); + if (att) + m_ref = att->getStable(); - fb_assert(optional || m_ref.hasData()); + fb_assert(type == UNNECESSARY || m_ref.hasData()); - if (m_ref.hasData()) - m_ref->getSync()->leave(); + if (m_ref.hasData()) + m_ref->getSync()->leave(); + } } ~EngineCheckout() @@ -1123,7 +1133,7 @@ namespace Jrd { { if (!m_mutex.tryEnter(from)) { - EngineCheckout cout(tdbb, from, optional); + EngineCheckout cout(tdbb, from, optional ? EngineCheckout::UNNECESSARY : EngineCheckout::REQUIRED); m_mutex.enter(from); } } @@ -1151,7 +1161,7 @@ namespace Jrd { { if (!m_sync.lockConditional(type, from)) { - EngineCheckout cout(tdbb, from, optional); + EngineCheckout cout(tdbb, from, optional ? EngineCheckout::UNNECESSARY : EngineCheckout::REQUIRED); m_sync.lock(type); } } diff --git a/src/jrd/met.epp b/src/jrd/met.epp index 662a2aa3d8..5cf27e5bb8 100644 --- a/src/jrd/met.epp +++ b/src/jrd/met.epp @@ -3207,7 +3207,7 @@ void MET_parse_sys_trigger(thread_db* tdbb, jrd_rel* relation) statement->triggerName = name; - statement->flags |= JrdStatement::FLAG_SYS_TRIGGER; + statement->flags |= JrdStatement::FLAG_SYS_TRIGGER | JrdStatement::FLAG_INTERNAL; if (trig_flags & TRG_ignore_perm) statement->flags |= JrdStatement::FLAG_IGNORE_PERM; diff --git a/src/jrd/names.h b/src/jrd/names.h index 1c81cafc3c..d43e08140d 100644 --- a/src/jrd/names.h +++ b/src/jrd/names.h @@ -460,3 +460,6 @@ NAME("RDB$KEYWORD_RESERVED", nam_keyword_reserved) NAME("MON$COMPILED_STATEMENTS", nam_mon_compiled_statements) NAME("MON$COMPILED_STATEMENT_ID", nam_mon_cmp_stmt_id) + +NAME("RDB$SHORT_DESCRIPTION", nam_short_description) +NAME("RDB$PROFILE_SESSION_ID", nam_prof_ses_id) diff --git a/src/jrd/opt.cpp b/src/jrd/opt.cpp index 35e777ab50..0884d0d95c 100644 --- a/src/jrd/opt.cpp +++ b/src/jrd/opt.cpp @@ -79,6 +79,7 @@ #include "../jrd/RecordSourceNodes.h" #include "../jrd/VirtualTable.h" #include "../jrd/Monitoring.h" +#include "../jrd/ProfilerManager.h" #include "../jrd/TimeZone.h" #include "../jrd/UserManagement.h" #include "../common/classes/array.h" @@ -462,7 +463,7 @@ string OPT_get_plan(thread_db* tdbb, const JrdStatement* statement, bool detaile for (FB_SIZE_T i = 0; i < fors.getCount(); i++) { plan += detailed ? "\nSelect Expression" : "\nPLAN "; - fors[i]->print(tdbb, plan, detailed, 0); + fors[i]->print(tdbb, plan, detailed, 0, true); } } diff --git a/src/jrd/os/posix/unix.cpp b/src/jrd/os/posix/unix.cpp index 443675e12b..7a14636775 100644 --- a/src/jrd/os/posix/unix.cpp +++ b/src/jrd/os/posix/unix.cpp @@ -318,7 +318,7 @@ void PIO_extend(thread_db* tdbb, jrd_file* main_file, const ULONG extPages, cons #if defined(HAVE_LINUX_FALLOC_H) && defined(HAVE_FALLOCATE) - EngineCheckout cout(tdbb, FB_FUNCTION, true); + EngineCheckout cout(tdbb, FB_FUNCTION, EngineCheckout::UNNECESSARY); ULONG leftPages = extPages; for (jrd_file* file = main_file; file && leftPages; file = file->fil_next) @@ -388,7 +388,7 @@ void PIO_flush(thread_db* tdbb, jrd_file* main_file) // Since all SUPERSERVER_V2 database and shadow I/O is synchronous, this is a no-op. #ifndef SUPERSERVER_V2 - EngineCheckout cout(tdbb, FB_FUNCTION, true); + EngineCheckout cout(tdbb, FB_FUNCTION, EngineCheckout::UNNECESSARY); MutexLockGuard guard(main_file->fil_mutex, FB_FUNCTION); for (jrd_file* file = main_file; file; file = file->fil_next) @@ -608,7 +608,7 @@ USHORT PIO_init_data(thread_db* tdbb, jrd_file* main_file, FbStatusVector* statu FB_UINT64 offset; - EngineCheckout cout(tdbb, FB_FUNCTION, true); + EngineCheckout cout(tdbb, FB_FUNCTION, EngineCheckout::UNNECESSARY); jrd_file* file = seek_file(main_file, &bdb, &offset, status_vector); @@ -756,7 +756,7 @@ bool PIO_read(thread_db* tdbb, jrd_file* file, BufferDesc* bdb, Ods::pag* page, Database* const dbb = tdbb->getDatabase(); - EngineCheckout cout(tdbb, FB_FUNCTION, true); + EngineCheckout cout(tdbb, FB_FUNCTION, EngineCheckout::UNNECESSARY); const SLONG size = dbb->dbb_page_size; @@ -808,7 +808,7 @@ bool PIO_write(thread_db* tdbb, jrd_file* file, BufferDesc* bdb, Ods::pag* page, Database* const dbb = tdbb->getDatabase(); - EngineCheckout cout(tdbb, FB_FUNCTION, true); + EngineCheckout cout(tdbb, FB_FUNCTION, EngineCheckout::UNNECESSARY); const SLONG size = dbb->dbb_page_size; diff --git a/src/jrd/os/win32/winnt.cpp b/src/jrd/os/win32/winnt.cpp index d7655adcde..ad88cc3b02 100644 --- a/src/jrd/os/win32/winnt.cpp +++ b/src/jrd/os/win32/winnt.cpp @@ -270,7 +270,7 @@ void PIO_extend(thread_db* tdbb, jrd_file* main_file, const ULONG extPages, cons if (!main_file->fil_ext_lock) return; - EngineCheckout cout(tdbb, FB_FUNCTION, true); + EngineCheckout cout(tdbb, FB_FUNCTION, EngineCheckout::UNNECESSARY); FileExtendLockGuard extLock(main_file->fil_ext_lock, true); ULONG leftPages = extPages; @@ -314,7 +314,7 @@ void PIO_flush(thread_db* tdbb, jrd_file* main_file) * Flush the operating system cache back to good, solid oxide. * **************************************/ - EngineCheckout cout(tdbb, FB_FUNCTION, true); + EngineCheckout cout(tdbb, FB_FUNCTION, EngineCheckout::UNNECESSARY); for (jrd_file* file = main_file; file; file = file->fil_next) FlushFileBuffers(file->fil_desc); @@ -440,7 +440,7 @@ USHORT PIO_init_data(thread_db* tdbb, jrd_file* main_file, FbStatusVector* statu Database* const dbb = tdbb->getDatabase(); - EngineCheckout cout(tdbb, FB_FUNCTION, true); + EngineCheckout cout(tdbb, FB_FUNCTION, EngineCheckout::UNNECESSARY); FileExtendLockGuard extLock(main_file->fil_ext_lock, false); // Fake buffer, used in seek_file. Page space ID doesn't matter there @@ -578,7 +578,7 @@ bool PIO_read(thread_db* tdbb, jrd_file* file, BufferDesc* bdb, Ods::pag* page, const DWORD size = dbb->dbb_page_size; - EngineCheckout cout(tdbb, FB_FUNCTION, true); + EngineCheckout cout(tdbb, FB_FUNCTION, EngineCheckout::UNNECESSARY); FileExtendLockGuard extLock(file->fil_ext_lock, false); OVERLAPPED overlapped; @@ -626,7 +626,7 @@ bool PIO_read_ahead(thread_db* tdbb, Database* const dbb = tdbb->getDatabase(); - EngineCheckout cout(tdbb, FB_FUNCTION, true); + EngineCheckout cout(tdbb, FB_FUNCTION, EngineCheckout::UNNECESSARY); // If an I/O status block was passed the caller wants to queue an asynchronous I/O. @@ -712,7 +712,7 @@ bool PIO_status(thread_db* tdbb, phys_io_blk* piob, FbStatusVector* status_vecto * Check the status of an asynchronous I/O. * **************************************/ - EngineCheckout cout(tdbb, FB_FUNCTION, true); + EngineCheckout cout(tdbb, FB_FUNCTION, EngineCheckout::UNNECESSARY); if (!(piob->piob_flags & PIOB_success)) { @@ -755,7 +755,7 @@ bool PIO_write(thread_db* tdbb, jrd_file* file, BufferDesc* bdb, Ods::pag* page, const DWORD size = dbb->dbb_page_size; - EngineCheckout cout(tdbb, FB_FUNCTION, true); + EngineCheckout cout(tdbb, FB_FUNCTION, EngineCheckout::UNNECESSARY); FileExtendLockGuard extLock(file->fil_ext_lock, false); OVERLAPPED overlapped; diff --git a/src/jrd/recsrc/AggregatedStream.cpp b/src/jrd/recsrc/AggregatedStream.cpp index d2592e03d0..116d67f454 100644 --- a/src/jrd/recsrc/AggregatedStream.cpp +++ b/src/jrd/recsrc/AggregatedStream.cpp @@ -52,7 +52,7 @@ BaseAggWinStream::BaseAggWinStream(thread_db* tdbb, Compiler } template -void BaseAggWinStream::open(thread_db* tdbb) const +void BaseAggWinStream::internalOpen(thread_db* tdbb) const { jrd_req* const request = tdbb->getRequest(); Impure* const impure = getImpure(request); @@ -365,15 +365,21 @@ AggregatedStream::AggregatedStream(thread_db* tdbb, CompilerScratch* csb, Stream fb_assert(map); } -void AggregatedStream::print(thread_db* tdbb, string& plan, bool detailed, unsigned level) const +void AggregatedStream::getChildren(Array& children) const +{ + children.add(m_next); +} + +void AggregatedStream::print(thread_db* tdbb, string& plan, bool detailed, unsigned level, bool recurse) const { if (detailed) plan += printIndent(++level) + "Aggregate"; - m_next->print(tdbb, plan, detailed, level); + if (recurse) + m_next->print(tdbb, plan, detailed, level, recurse); } -bool AggregatedStream::getRecord(thread_db* tdbb) const +bool AggregatedStream::internalGetRecord(thread_db* tdbb) const { JRD_reschedule(tdbb); diff --git a/src/jrd/recsrc/BitmapTableScan.cpp b/src/jrd/recsrc/BitmapTableScan.cpp index 6b3485eda7..1688a85227 100644 --- a/src/jrd/recsrc/BitmapTableScan.cpp +++ b/src/jrd/recsrc/BitmapTableScan.cpp @@ -46,7 +46,7 @@ BitmapTableScan::BitmapTableScan(CompilerScratch* csb, const string& alias, m_impure = csb->allocImpure(); } -void BitmapTableScan::open(thread_db* tdbb) const +void BitmapTableScan::internalOpen(thread_db* tdbb) const { jrd_req* const request = tdbb->getRequest(); Impure* const impure = request->getImpure(m_impure); @@ -80,7 +80,7 @@ void BitmapTableScan::close(thread_db* tdbb) const } } -bool BitmapTableScan::getRecord(thread_db* tdbb) const +bool BitmapTableScan::internalGetRecord(thread_db* tdbb) const { JRD_reschedule(tdbb); @@ -121,8 +121,12 @@ bool BitmapTableScan::getRecord(thread_db* tdbb) const return false; } +void BitmapTableScan::getChildren(Array& children) const +{ +} + void BitmapTableScan::print(thread_db* tdbb, string& plan, - bool detailed, unsigned level) const + bool detailed, unsigned level, bool recurse) const { if (detailed) { diff --git a/src/jrd/recsrc/BufferedStream.cpp b/src/jrd/recsrc/BufferedStream.cpp index 5bfad5b7ff..f196a81815 100644 --- a/src/jrd/recsrc/BufferedStream.cpp +++ b/src/jrd/recsrc/BufferedStream.cpp @@ -40,7 +40,9 @@ using namespace Jrd; // -------------------------- BufferedStream::BufferedStream(CompilerScratch* csb, RecordSource* next) - : m_next(next), m_map(csb->csb_pool) + : BaseBufferedStream(csb), + m_next(next), + m_map(csb->csb_pool) { fb_assert(m_next); @@ -112,7 +114,7 @@ BufferedStream::BufferedStream(CompilerScratch* csb, RecordSource* next) m_format = format; } -void BufferedStream::open(thread_db* tdbb) const +void BufferedStream::internalOpen(thread_db* tdbb) const { jrd_req* const request = tdbb->getRequest(); Impure* const impure = request->getImpure(m_impure); @@ -147,7 +149,7 @@ void BufferedStream::close(thread_db* tdbb) const } } -bool BufferedStream::getRecord(thread_db* tdbb) const +bool BufferedStream::internalGetRecord(thread_db* tdbb) const { JRD_reschedule(tdbb); @@ -311,7 +313,12 @@ bool BufferedStream::lockRecord(thread_db* tdbb) const return m_next->lockRecord(tdbb); } -void BufferedStream::print(thread_db* tdbb, string& plan, bool detailed, unsigned level) const +void BufferedStream::getChildren(Array& children) const +{ + children.add(m_next); +} + +void BufferedStream::print(thread_db* tdbb, string& plan, bool detailed, unsigned level, bool recurse) const { if (detailed) { @@ -321,7 +328,8 @@ void BufferedStream::print(thread_db* tdbb, string& plan, bool detailed, unsigne plan += printIndent(++level) + "Record Buffer" + extras; } - m_next->print(tdbb, plan, detailed, level); + if (recurse) + m_next->print(tdbb, plan, detailed, level, recurse); } void BufferedStream::markRecursive() diff --git a/src/jrd/recsrc/ConditionalStream.cpp b/src/jrd/recsrc/ConditionalStream.cpp index 91d3e85403..2e5842d5d9 100644 --- a/src/jrd/recsrc/ConditionalStream.cpp +++ b/src/jrd/recsrc/ConditionalStream.cpp @@ -41,14 +41,17 @@ using namespace Jrd; ConditionalStream::ConditionalStream(CompilerScratch* csb, RecordSource* first, RecordSource* second, BoolExprNode* boolean) - : m_first(first), m_second(second), m_boolean(boolean) + : RecordSource(csb), + m_first(first), + m_second(second), + m_boolean(boolean) { fb_assert(m_first && m_second && m_boolean); m_impure = csb->allocImpure(); } -void ConditionalStream::open(thread_db* tdbb) const +void ConditionalStream::internalOpen(thread_db* tdbb) const { jrd_req* const request = tdbb->getRequest(); Impure* const impure = request->getImpure(m_impure); @@ -75,7 +78,7 @@ void ConditionalStream::close(thread_db* tdbb) const } } -bool ConditionalStream::getRecord(thread_db* tdbb) const +bool ConditionalStream::internalGetRecord(thread_db* tdbb) const { JRD_reschedule(tdbb); @@ -110,24 +113,34 @@ bool ConditionalStream::lockRecord(thread_db* tdbb) const return impure->irsb_next->lockRecord(tdbb); } -void ConditionalStream::print(thread_db* tdbb, string& plan, bool detailed, unsigned level) const +void ConditionalStream::getChildren(Array& children) const +{ + children.add(m_first); + children.add(m_second); +} + +void ConditionalStream::print(thread_db* tdbb, string& plan, bool detailed, unsigned level, bool recurse) const { if (detailed) { plan += printIndent(++level) + "Condition"; - m_first->print(tdbb, plan, true, level); - m_second->print(tdbb, plan, true, level); + + if (recurse) + { + m_first->print(tdbb, plan, true, level, recurse); + m_second->print(tdbb, plan, true, level, recurse); + } } else { if (!level) plan += "("; - m_first->print(tdbb, plan, false, level + 1); + m_first->print(tdbb, plan, false, level + 1, recurse); plan += ", "; - m_second->print(tdbb, plan, false, level + 1); + m_second->print(tdbb, plan, false, level + 1, recurse); if (!level) plan += ")"; diff --git a/src/jrd/recsrc/Cursor.cpp b/src/jrd/recsrc/Cursor.cpp index 0f1263b87b..cb452db22c 100644 --- a/src/jrd/recsrc/Cursor.cpp +++ b/src/jrd/recsrc/Cursor.cpp @@ -23,6 +23,7 @@ #include "firebird.h" #include "../jrd/jrd.h" #include "../jrd/req.h" +#include "../jrd/ProfilerManager.h" #include "../jrd/cmp_proto.h" #include "RecordSource.h" @@ -105,7 +106,9 @@ Cursor::Cursor(CompilerScratch* csb, const RecordSource* rsb, void Cursor::open(thread_db* tdbb) const { - jrd_req* const request = tdbb->getRequest(); + const auto request = tdbb->getRequest(); + const auto attachment = tdbb->getAttachment(); + Impure* impure = request->getImpure(m_impure); impure->irsb_active = true; @@ -132,7 +135,9 @@ bool Cursor::fetchNext(thread_db* tdbb) const if (!validate(tdbb)) return false; - jrd_req* const request = tdbb->getRequest(); + const auto request = tdbb->getRequest(); + const auto attachment = tdbb->getAttachment(); + Impure* const impure = request->getImpure(m_impure); if (!impure->irsb_active) @@ -197,7 +202,9 @@ bool Cursor::fetchPrior(thread_db* tdbb) const if (!validate(tdbb)) return false; - jrd_req* const request = tdbb->getRequest(); + const auto request = tdbb->getRequest(); + const auto attachment = tdbb->getAttachment(); + Impure* const impure = request->getImpure(m_impure); if (!impure->irsb_active) @@ -274,7 +281,9 @@ bool Cursor::fetchAbsolute(thread_db* tdbb, SINT64 offset) const if (!validate(tdbb)) return false; - jrd_req* const request = tdbb->getRequest(); + const auto request = tdbb->getRequest(); + const auto attachment = tdbb->getAttachment(); + Impure* const impure = request->getImpure(m_impure); if (!impure->irsb_active) @@ -323,7 +332,9 @@ bool Cursor::fetchRelative(thread_db* tdbb, SINT64 offset) const if (!validate(tdbb)) return false; - jrd_req* const request = tdbb->getRequest(); + const auto request = tdbb->getRequest(); + const auto attachment = tdbb->getAttachment(); + Impure* const impure = request->getImpure(m_impure); if (!impure->irsb_active) diff --git a/src/jrd/recsrc/ExternalTableScan.cpp b/src/jrd/recsrc/ExternalTableScan.cpp index 6b080c7289..2eb8a0082e 100644 --- a/src/jrd/recsrc/ExternalTableScan.cpp +++ b/src/jrd/recsrc/ExternalTableScan.cpp @@ -45,7 +45,7 @@ ExternalTableScan::ExternalTableScan(CompilerScratch* csb, const string& alias, m_impure = csb->allocImpure(); } -void ExternalTableScan::open(thread_db* tdbb) const +void ExternalTableScan::internalOpen(thread_db* tdbb) const { Database* const dbb = tdbb->getDatabase(); jrd_req* const request = tdbb->getRequest(); @@ -76,7 +76,7 @@ void ExternalTableScan::close(thread_db* tdbb) const impure->irsb_flags &= ~irsb_open; } -bool ExternalTableScan::getRecord(thread_db* tdbb) const +bool ExternalTableScan::internalGetRecord(thread_db* tdbb) const { JRD_reschedule(tdbb); @@ -116,8 +116,12 @@ bool ExternalTableScan::lockRecord(thread_db* tdbb) const return false; // compiler silencer } +void ExternalTableScan::getChildren(Array& children) const +{ +} + void ExternalTableScan::print(thread_db* tdbb, string& plan, - bool detailed, unsigned level) const + bool detailed, unsigned level, bool recurse) const { if (detailed) { diff --git a/src/jrd/recsrc/FilteredStream.cpp b/src/jrd/recsrc/FilteredStream.cpp index 5ae71e8803..014865ef69 100644 --- a/src/jrd/recsrc/FilteredStream.cpp +++ b/src/jrd/recsrc/FilteredStream.cpp @@ -36,15 +36,20 @@ using namespace Jrd; // ------------------------------------ FilteredStream::FilteredStream(CompilerScratch* csb, RecordSource* next, BoolExprNode* boolean) - : m_next(next), m_boolean(boolean), m_anyBoolean(NULL), - m_ansiAny(false), m_ansiAll(false), m_ansiNot(false) + : RecordSource(csb), + m_next(next), + m_boolean(boolean), + m_anyBoolean(NULL), + m_ansiAny(false), + m_ansiAll(false), + m_ansiNot(false) { fb_assert(m_next && m_boolean); m_impure = csb->allocImpure(); } -void FilteredStream::open(thread_db* tdbb) const +void FilteredStream::internalOpen(thread_db* tdbb) const { jrd_req* const request = tdbb->getRequest(); Impure* const impure = request->getImpure(m_impure); @@ -70,7 +75,7 @@ void FilteredStream::close(thread_db* tdbb) const } } -bool FilteredStream::getRecord(thread_db* tdbb) const +bool FilteredStream::internalGetRecord(thread_db* tdbb) const { JRD_reschedule(tdbb); @@ -102,12 +107,18 @@ bool FilteredStream::lockRecord(thread_db* tdbb) const return m_next->lockRecord(tdbb); } -void FilteredStream::print(thread_db* tdbb, string& plan, bool detailed, unsigned level) const +void FilteredStream::getChildren(Array& children) const +{ + children.add(m_next); +} + +void FilteredStream::print(thread_db* tdbb, string& plan, bool detailed, unsigned level, bool recurse) const { if (detailed) plan += printIndent(++level) + "Filter"; - m_next->print(tdbb, plan, detailed, level); + if (recurse) + m_next->print(tdbb, plan, detailed, level, recurse); } void FilteredStream::markRecursive() diff --git a/src/jrd/recsrc/FirstRowsStream.cpp b/src/jrd/recsrc/FirstRowsStream.cpp index f5deb5328c..7e698bdc76 100644 --- a/src/jrd/recsrc/FirstRowsStream.cpp +++ b/src/jrd/recsrc/FirstRowsStream.cpp @@ -37,14 +37,16 @@ using namespace Jrd; // -------------------------------- FirstRowsStream::FirstRowsStream(CompilerScratch* csb, RecordSource* next, ValueExprNode* value) - : m_next(next), m_value(value) + : RecordSource(csb), + m_next(next), + m_value(value) { fb_assert(m_next && m_value); m_impure = csb->allocImpure(); } -void FirstRowsStream::open(thread_db* tdbb) const +void FirstRowsStream::internalOpen(thread_db* tdbb) const { jrd_req* const request = tdbb->getRequest(); Impure* const impure = request->getImpure(m_impure); @@ -81,7 +83,7 @@ void FirstRowsStream::close(thread_db* tdbb) const } } -bool FirstRowsStream::getRecord(thread_db* tdbb) const +bool FirstRowsStream::internalGetRecord(thread_db* tdbb) const { JRD_reschedule(tdbb); @@ -112,12 +114,18 @@ bool FirstRowsStream::lockRecord(thread_db* tdbb) const return m_next->lockRecord(tdbb); } -void FirstRowsStream::print(thread_db* tdbb, string& plan, bool detailed, unsigned level) const +void FirstRowsStream::getChildren(Array& children) const +{ + children.add(m_next); +} + +void FirstRowsStream::print(thread_db* tdbb, string& plan, bool detailed, unsigned level, bool recurse) const { if (detailed) plan += printIndent(++level) + "First N Records"; - m_next->print(tdbb, plan, detailed, level); + if (recurse) + m_next->print(tdbb, plan, detailed, level, recurse); } void FirstRowsStream::markRecursive() diff --git a/src/jrd/recsrc/FullOuterJoin.cpp b/src/jrd/recsrc/FullOuterJoin.cpp index 73b77f8f05..93def52292 100644 --- a/src/jrd/recsrc/FullOuterJoin.cpp +++ b/src/jrd/recsrc/FullOuterJoin.cpp @@ -38,14 +38,16 @@ using namespace Jrd; // ---------------------------- FullOuterJoin::FullOuterJoin(CompilerScratch* csb, RecordSource* arg1, RecordSource* arg2) - : m_arg1(arg1), m_arg2(arg2) + : RecordSource(csb), + m_arg1(arg1), + m_arg2(arg2) { fb_assert(m_arg1 && m_arg2); m_impure = csb->allocImpure(); } -void FullOuterJoin::open(thread_db* tdbb) const +void FullOuterJoin::internalOpen(thread_db* tdbb) const { jrd_req* const request = tdbb->getRequest(); Impure* const impure = request->getImpure(m_impure); @@ -74,7 +76,7 @@ void FullOuterJoin::close(thread_db* tdbb) const } } -bool FullOuterJoin::getRecord(thread_db* tdbb) const +bool FullOuterJoin::internalGetRecord(thread_db* tdbb) const { JRD_reschedule(tdbb); @@ -110,21 +112,31 @@ bool FullOuterJoin::lockRecord(thread_db* tdbb) const return false; // compiler silencer } -void FullOuterJoin::print(thread_db* tdbb, string& plan, bool detailed, unsigned level) const +void FullOuterJoin::getChildren(Array& children) const +{ + children.add(m_arg1); + children.add(m_arg2); +} + +void FullOuterJoin::print(thread_db* tdbb, string& plan, bool detailed, unsigned level, bool recurse) const { if (detailed) { plan += printIndent(++level) + "Full Outer Join"; - m_arg1->print(tdbb, plan, true, level); - m_arg2->print(tdbb, plan, true, level); + + if (recurse) + { + m_arg1->print(tdbb, plan, true, level, recurse); + m_arg2->print(tdbb, plan, true, level, recurse); + } } else { level++; plan += "JOIN ("; - m_arg1->print(tdbb, plan, false, level); + m_arg1->print(tdbb, plan, false, level, recurse); plan += ", "; - m_arg2->print(tdbb, plan, false, level); + m_arg2->print(tdbb, plan, false, level, recurse); plan += ")"; } } diff --git a/src/jrd/recsrc/FullTableScan.cpp b/src/jrd/recsrc/FullTableScan.cpp index d8096fea15..a536857c39 100644 --- a/src/jrd/recsrc/FullTableScan.cpp +++ b/src/jrd/recsrc/FullTableScan.cpp @@ -47,7 +47,7 @@ FullTableScan::FullTableScan(CompilerScratch* csb, const string& alias, m_impure = csb->allocImpure(); } -void FullTableScan::open(thread_db* tdbb) const +void FullTableScan::internalOpen(thread_db* tdbb) const { Database* const dbb = tdbb->getDatabase(); Attachment* const attachment = tdbb->getAttachment(); @@ -131,7 +131,7 @@ void FullTableScan::close(thread_db* tdbb) const } } -bool FullTableScan::getRecord(thread_db* tdbb) const +bool FullTableScan::internalGetRecord(thread_db* tdbb) const { JRD_reschedule(tdbb); @@ -161,7 +161,11 @@ bool FullTableScan::getRecord(thread_db* tdbb) const return false; } -void FullTableScan::print(thread_db* tdbb, string& plan, bool detailed, unsigned level) const +void FullTableScan::getChildren(Array& children) const +{ +} + +void FullTableScan::print(thread_db* tdbb, string& plan, bool detailed, unsigned level, bool recurse) const { if (detailed) { diff --git a/src/jrd/recsrc/HashJoin.cpp b/src/jrd/recsrc/HashJoin.cpp index 5dab9e12f4..ea9e87f8cb 100644 --- a/src/jrd/recsrc/HashJoin.cpp +++ b/src/jrd/recsrc/HashJoin.cpp @@ -209,7 +209,8 @@ private: HashJoin::HashJoin(thread_db* tdbb, CompilerScratch* csb, FB_SIZE_T count, RecordSource* const* args, NestValueArray* const* keys) - : m_args(csb->csb_pool, count - 1) + : RecordSource(csb), + m_args(csb->csb_pool, count - 1) { fb_assert(count >= 2); @@ -281,7 +282,7 @@ HashJoin::HashJoin(thread_db* tdbb, CompilerScratch* csb, FB_SIZE_T count, } } -void HashJoin::open(thread_db* tdbb) const +void HashJoin::internalOpen(thread_db* tdbb) const { jrd_req* const request = tdbb->getRequest(); Impure* const impure = request->getImpure(m_impure); @@ -346,7 +347,7 @@ void HashJoin::close(thread_db* tdbb) const } } -bool HashJoin::getRecord(thread_db* tdbb) const +bool HashJoin::internalGetRecord(thread_db* tdbb) const { JRD_reschedule(tdbb); @@ -426,29 +427,40 @@ bool HashJoin::lockRecord(thread_db* /*tdbb*/) const return false; // compiler silencer } -void HashJoin::print(thread_db* tdbb, string& plan, bool detailed, unsigned level) const +void HashJoin::getChildren(Array& children) const +{ + children.add(m_leader.source); + + for (FB_SIZE_T i = 0; i < m_args.getCount(); i++) + children.add(m_args[i].source); +} + +void HashJoin::print(thread_db* tdbb, string& plan, bool detailed, unsigned level, bool recurse) const { if (detailed) { plan += printIndent(++level) + "Hash Join (inner)"; - m_leader.source->print(tdbb, plan, true, level); + if (recurse) + { + m_leader.source->print(tdbb, plan, true, level, recurse); - for (FB_SIZE_T i = 0; i < m_args.getCount(); i++) - m_args[i].source->print(tdbb, plan, true, level); + for (FB_SIZE_T i = 0; i < m_args.getCount(); i++) + m_args[i].source->print(tdbb, plan, true, level, recurse); + } } else { level++; plan += "HASH ("; - m_leader.source->print(tdbb, plan, false, level); + m_leader.source->print(tdbb, plan, false, level, recurse); plan += ", "; for (FB_SIZE_T i = 0; i < m_args.getCount(); i++) { if (i) plan += ", "; - m_args[i].source->print(tdbb, plan, false, level); + m_args[i].source->print(tdbb, plan, false, level, recurse); } plan += ")"; } diff --git a/src/jrd/recsrc/IndexTableScan.cpp b/src/jrd/recsrc/IndexTableScan.cpp index f281cc0024..0c5b12f936 100644 --- a/src/jrd/recsrc/IndexTableScan.cpp +++ b/src/jrd/recsrc/IndexTableScan.cpp @@ -60,7 +60,7 @@ IndexTableScan::IndexTableScan(CompilerScratch* csb, const string& alias, m_impure = csb->allocImpure(FB_ALIGNMENT, static_cast(size)); } -void IndexTableScan::open(thread_db* tdbb) const +void IndexTableScan::internalOpen(thread_db* tdbb) const { jrd_req* const request = tdbb->getRequest(); Impure* const impure = request->getImpure(m_impure); @@ -123,7 +123,7 @@ void IndexTableScan::close(thread_db* tdbb) const #endif } -bool IndexTableScan::getRecord(thread_db* tdbb) const +bool IndexTableScan::internalGetRecord(thread_db* tdbb) const { JRD_reschedule(tdbb); @@ -263,7 +263,11 @@ bool IndexTableScan::getRecord(thread_db* tdbb) const return false; } -void IndexTableScan::print(thread_db* tdbb, string& plan, bool detailed, unsigned level) const +void IndexTableScan::getChildren(Array& children) const +{ +} + +void IndexTableScan::print(thread_db* tdbb, string& plan, bool detailed, unsigned level, bool recurse) const { if (detailed) { diff --git a/src/jrd/recsrc/LocalTableStream.cpp b/src/jrd/recsrc/LocalTableStream.cpp index 727a3934d9..70bfae2f1e 100644 --- a/src/jrd/recsrc/LocalTableStream.cpp +++ b/src/jrd/recsrc/LocalTableStream.cpp @@ -44,7 +44,7 @@ LocalTableStream::LocalTableStream(CompilerScratch* csb, StreamType stream, cons m_impure = csb->allocImpure(); } -void LocalTableStream::open(thread_db* tdbb) const +void LocalTableStream::internalOpen(thread_db* tdbb) const { const auto request = tdbb->getRequest(); const auto impure = request->getImpure(m_impure); @@ -69,7 +69,11 @@ void LocalTableStream::close(thread_db* tdbb) const impure->irsb_flags &= ~irsb_open; } -bool LocalTableStream::getRecord(thread_db* tdbb) const +void LocalTableStream::getChildren(Array& children) const +{ +} + +bool LocalTableStream::internalGetRecord(thread_db* tdbb) const { JRD_reschedule(tdbb); @@ -108,7 +112,7 @@ bool LocalTableStream::lockRecord(thread_db* tdbb) const return false; // compiler silencer } -void LocalTableStream::print(thread_db* tdbb, string& plan, bool detailed, unsigned level) const +void LocalTableStream::print(thread_db* tdbb, string& plan, bool detailed, unsigned level, bool recurse) const { //// TODO: Use Local Table name/alias. diff --git a/src/jrd/recsrc/LockedStream.cpp b/src/jrd/recsrc/LockedStream.cpp index a4d389a4a4..0c63c28dd8 100644 --- a/src/jrd/recsrc/LockedStream.cpp +++ b/src/jrd/recsrc/LockedStream.cpp @@ -35,14 +35,15 @@ using namespace Jrd; // ------------------------------------ LockedStream::LockedStream(CompilerScratch* csb, RecordSource* next) - : m_next(next) + : RecordSource(csb), + m_next(next) { fb_assert(m_next); m_impure = csb->allocImpure(); } -void LockedStream::open(thread_db* tdbb) const +void LockedStream::internalOpen(thread_db* tdbb) const { jrd_req* const request = tdbb->getRequest(); Impure* const impure = request->getImpure(m_impure); @@ -68,7 +69,7 @@ void LockedStream::close(thread_db* tdbb) const } } -bool LockedStream::getRecord(thread_db* tdbb) const +bool LockedStream::internalGetRecord(thread_db* tdbb) const { JRD_reschedule(tdbb); @@ -102,12 +103,18 @@ bool LockedStream::lockRecord(thread_db* tdbb) const return m_next->lockRecord(tdbb); } -void LockedStream::print(thread_db* tdbb, string& plan, bool detailed, unsigned level) const +void LockedStream::getChildren(Array& children) const +{ + children.add(m_next); +} + +void LockedStream::print(thread_db* tdbb, string& plan, bool detailed, unsigned level, bool recurse) const { if (detailed) plan += printIndent(++level) + "Write Lock"; - m_next->print(tdbb, plan, detailed, level); + if (recurse) + m_next->print(tdbb, plan, detailed, level, recurse); } void LockedStream::markRecursive() diff --git a/src/jrd/recsrc/MergeJoin.cpp b/src/jrd/recsrc/MergeJoin.cpp index dbfd555577..fef8f381a2 100644 --- a/src/jrd/recsrc/MergeJoin.cpp +++ b/src/jrd/recsrc/MergeJoin.cpp @@ -38,7 +38,9 @@ static const char* const SCRATCH = "fb_merge_"; MergeJoin::MergeJoin(CompilerScratch* csb, FB_SIZE_T count, SortedStream* const* args, const NestValueArray* const* keys) - : m_args(csb->csb_pool), m_keys(csb->csb_pool) + : RecordSource(csb), + m_args(csb->csb_pool), + m_keys(csb->csb_pool) { const size_t size = sizeof(struct Impure) + count * sizeof(Impure::irsb_mrg_repeat); m_impure = csb->allocImpure(FB_ALIGNMENT, static_cast(size)); @@ -56,7 +58,7 @@ MergeJoin::MergeJoin(CompilerScratch* csb, FB_SIZE_T count, } } -void MergeJoin::open(thread_db* tdbb) const +void MergeJoin::internalOpen(thread_db* tdbb) const { jrd_req* const request = tdbb->getRequest(); Impure* const impure = request->getImpure(m_impure); @@ -125,7 +127,7 @@ void MergeJoin::close(thread_db* tdbb) const } } -bool MergeJoin::getRecord(thread_db* tdbb) const +bool MergeJoin::internalGetRecord(thread_db* tdbb) const { JRD_reschedule(tdbb); @@ -180,7 +182,7 @@ bool MergeJoin::getRecord(thread_db* tdbb) const { mfb->mfb_current_block = 0; mfb->mfb_equal_records = 0; - if ((record = getRecord(tdbb, i)) < 0) + if ((record = getRecordByIndex(tdbb, i)) < 0) return false; } @@ -224,7 +226,7 @@ bool MergeJoin::getRecord(thread_db* tdbb) const mfb->mfb_current_block = 0; mfb->mfb_equal_records = 0; - const SLONG record = getRecord(tdbb, i); + const SLONG record = getRecordByIndex(tdbb, i); if (record < 0) return false; @@ -255,7 +257,7 @@ bool MergeJoin::getRecord(thread_db* tdbb) const memcpy(first_data, getData(tdbb, mfb, 0), key_length); SLONG record; - while ((record = getRecord(tdbb, i)) >= 0) + while ((record = getRecordByIndex(tdbb, i)) >= 0) { const UCHAR* p = first_data; const UCHAR* q = getData(tdbb, mfb, record); @@ -336,14 +338,23 @@ bool MergeJoin::lockRecord(thread_db* /*tdbb*/) const return false; // compiler silencer } -void MergeJoin::print(thread_db* tdbb, string& plan, bool detailed, unsigned level) const +void MergeJoin::getChildren(Array& children) const +{ + for (FB_SIZE_T i = 0; i < m_args.getCount(); i++) + children.add(m_args[i]); +} + +void MergeJoin::print(thread_db* tdbb, string& plan, bool detailed, unsigned level, bool recurse) const { if (detailed) { plan += printIndent(++level) + "Merge Join (inner)"; - for (FB_SIZE_T i = 0; i < m_args.getCount(); i++) - m_args[i]->print(tdbb, plan, true, level); + if (recurse) + { + for (FB_SIZE_T i = 0; i < m_args.getCount(); i++) + m_args[i]->print(tdbb, plan, true, level, recurse); + } } else { @@ -354,7 +365,7 @@ void MergeJoin::print(thread_db* tdbb, string& plan, bool detailed, unsigned lev if (i) plan += ", "; - m_args[i]->print(tdbb, plan, false, level); + m_args[i]->print(tdbb, plan, false, level, recurse); } plan += ")"; } @@ -434,7 +445,7 @@ UCHAR* MergeJoin::getData(thread_db* /*tdbb*/, MergeFile* mfb, SLONG record) con return mfb->mfb_block_data + merge_offset; } -SLONG MergeJoin::getRecord(thread_db* tdbb, FB_SIZE_T index) const +SLONG MergeJoin::getRecordByIndex(thread_db* tdbb, FB_SIZE_T index) const { jrd_req* const request = tdbb->getRequest(); Impure* const impure = request->getImpure(m_impure); diff --git a/src/jrd/recsrc/NestedLoopJoin.cpp b/src/jrd/recsrc/NestedLoopJoin.cpp index 0446f601b7..cfd3d2a374 100644 --- a/src/jrd/recsrc/NestedLoopJoin.cpp +++ b/src/jrd/recsrc/NestedLoopJoin.cpp @@ -35,7 +35,10 @@ using namespace Jrd; // ------------------------------ NestedLoopJoin::NestedLoopJoin(CompilerScratch* csb, FB_SIZE_T count, RecordSource* const* args) - : m_joinType(INNER_JOIN), m_args(csb->csb_pool), m_boolean(NULL) + : RecordSource(csb), + m_joinType(INNER_JOIN), + m_args(csb->csb_pool), + m_boolean(NULL) { m_impure = csb->allocImpure(); @@ -47,7 +50,10 @@ NestedLoopJoin::NestedLoopJoin(CompilerScratch* csb, FB_SIZE_T count, RecordSour NestedLoopJoin::NestedLoopJoin(CompilerScratch* csb, RecordSource* outer, RecordSource* inner, BoolExprNode* boolean, JoinType joinType) - : m_joinType(joinType), m_args(csb->csb_pool), m_boolean(boolean) + : RecordSource(csb), + m_joinType(joinType), + m_args(csb->csb_pool), + m_boolean(boolean) { fb_assert(outer && inner); @@ -57,7 +63,7 @@ NestedLoopJoin::NestedLoopJoin(CompilerScratch* csb, RecordSource* outer, Record m_args.add(inner); } -void NestedLoopJoin::open(thread_db* tdbb) const +void NestedLoopJoin::internalOpen(thread_db* tdbb) const { jrd_req* const request = tdbb->getRequest(); Impure* const impure = request->getImpure(m_impure); @@ -82,7 +88,7 @@ void NestedLoopJoin::close(thread_db* tdbb) const } } -bool NestedLoopJoin::getRecord(thread_db* tdbb) const +bool NestedLoopJoin::internalGetRecord(thread_db* tdbb) const { JRD_reschedule(tdbb); @@ -196,7 +202,13 @@ bool NestedLoopJoin::lockRecord(thread_db* /*tdbb*/) const return false; // compiler silencer } -void NestedLoopJoin::print(thread_db* tdbb, string& plan, bool detailed, unsigned level) const +void NestedLoopJoin::getChildren(Array& children) const +{ + for (FB_SIZE_T i = 0; i < m_args.getCount(); i++) + children.add(m_args[i]); +} + +void NestedLoopJoin::print(thread_db* tdbb, string& plan, bool detailed, unsigned level, bool recurse) const { if (m_args.hasData()) { @@ -226,8 +238,11 @@ void NestedLoopJoin::print(thread_db* tdbb, string& plan, bool detailed, unsigne fb_assert(false); } - for (FB_SIZE_T i = 0; i < m_args.getCount(); i++) - m_args[i]->print(tdbb, plan, true, level); + if (recurse) + { + for (FB_SIZE_T i = 0; i < m_args.getCount(); i++) + m_args[i]->print(tdbb, plan, true, level, recurse); + } } else { @@ -238,7 +253,7 @@ void NestedLoopJoin::print(thread_db* tdbb, string& plan, bool detailed, unsigne if (i) plan += ", "; - m_args[i]->print(tdbb, plan, false, level); + m_args[i]->print(tdbb, plan, false, level, recurse); } plan += ")"; } diff --git a/src/jrd/recsrc/ProcedureScan.cpp b/src/jrd/recsrc/ProcedureScan.cpp index e80cd642f4..f5003973bc 100644 --- a/src/jrd/recsrc/ProcedureScan.cpp +++ b/src/jrd/recsrc/ProcedureScan.cpp @@ -53,7 +53,7 @@ ProcedureScan::ProcedureScan(CompilerScratch* csb, const string& alias, StreamTy fb_assert(sourceList->items.getCount() == targetList->items.getCount()); } -void ProcedureScan::open(thread_db* tdbb) const +void ProcedureScan::internalOpen(thread_db* tdbb) const { if (!m_procedure->isImplemented()) { @@ -159,7 +159,7 @@ void ProcedureScan::close(thread_db* tdbb) const } } -bool ProcedureScan::getRecord(thread_db* tdbb) const +bool ProcedureScan::internalGetRecord(thread_db* tdbb) const { JRD_reschedule(tdbb); @@ -241,7 +241,11 @@ bool ProcedureScan::lockRecord(thread_db* /*tdbb*/) const return false; // compiler silencer } -void ProcedureScan::print(thread_db* tdbb, string& plan, bool detailed, unsigned level) const +void ProcedureScan::getChildren(Array& children) const +{ +} + +void ProcedureScan::print(thread_db* tdbb, string& plan, bool detailed, unsigned level, bool recurse) const { if (detailed) { diff --git a/src/jrd/recsrc/RecordSource.cpp b/src/jrd/recsrc/RecordSource.cpp index 425255707d..9408856fc5 100644 --- a/src/jrd/recsrc/RecordSource.cpp +++ b/src/jrd/recsrc/RecordSource.cpp @@ -26,6 +26,7 @@ #include "../jrd/intl.h" #include "../jrd/req.h" #include "../jrd/rse.h" +#include "../jrd/ProfilerManager.h" #include "../jrd/cmp_proto.h" #include "../jrd/dpm_proto.h" #include "../jrd/err_proto.h" @@ -44,6 +45,72 @@ using namespace Jrd; // Record source class // ------------------- +RecordSource::RecordSource(CompilerScratch* csb) + : m_impure(0), + m_cursorProfileId(csb->csb_currentCursorProfileId), + m_recSourceProfileId(csb->csb_nextRecSourceProfileId++), + m_recursive(false) +{ +} + +void RecordSource::open(thread_db* tdbb) const +{ + const auto attachment = tdbb->getAttachment(); + const auto request = tdbb->getRequest(); + + const auto profilerManager = attachment->isProfilerActive() && !request->hasInternalStatement() ? + attachment->getProfilerManager(tdbb) : + nullptr; + + const SINT64 lastPerfCounter = profilerManager ? + fb_utils::query_performance_counter() : + 0; + + if (profilerManager) + { + profilerManager->prepareRecSource(tdbb, request, this); + profilerManager->beforeRecordSourceOpen(request, this); + } + + internalOpen(tdbb); + + if (profilerManager) + { + const SINT64 currentPerfCounter = fb_utils::query_performance_counter(); + profilerManager->afterRecordSourceOpen(request, this, currentPerfCounter - lastPerfCounter); + } +} + +bool RecordSource::getRecord(thread_db* tdbb) const +{ + const auto attachment = tdbb->getAttachment(); + const auto request = tdbb->getRequest(); + + const auto profilerManager = attachment->isProfilerActive() && !request->hasInternalStatement() ? + attachment->getProfilerManager(tdbb) : + nullptr; + + const SINT64 lastPerfCounter = profilerManager ? + fb_utils::query_performance_counter() : + 0; + + if (profilerManager) + { + profilerManager->prepareRecSource(tdbb, request, this); + profilerManager->beforeRecordSourceGetRecord(request, this); + } + + const auto ret = internalGetRecord(tdbb); + + if (profilerManager) + { + const SINT64 currentPerfCounter = fb_utils::query_performance_counter(); + profilerManager->afterRecordSourceGetRecord(request, this, currentPerfCounter - lastPerfCounter); + } + + return ret; +} + string RecordSource::printName(thread_db* tdbb, const string& name, bool quote) { const string result(name.c_str(), name.length()); @@ -182,7 +249,9 @@ RecordSource::~RecordSource() // ------------------ RecordStream::RecordStream(CompilerScratch* csb, StreamType stream, const Format* format) - : m_stream(stream), m_format(format ? format : csb->csb_rpt[stream].csb_format) + : RecordSource(csb), + m_stream(stream), + m_format(format ? format : csb->csb_rpt[stream].csb_format) { fb_assert(m_format); } diff --git a/src/jrd/recsrc/RecordSource.h b/src/jrd/recsrc/RecordSource.h index 83b1b7777b..33001c22c8 100644 --- a/src/jrd/recsrc/RecordSource.h +++ b/src/jrd/recsrc/RecordSource.h @@ -58,15 +58,15 @@ namespace Jrd class RecordSource { public: - virtual void open(thread_db* tdbb) const = 0; virtual void close(thread_db* tdbb) const = 0; - virtual bool getRecord(thread_db* tdbb) const = 0; virtual bool refetchRecord(thread_db* tdbb) const = 0; virtual bool lockRecord(thread_db* tdbb) const = 0; + virtual void getChildren(Firebird::Array& children) const = 0; + virtual void print(thread_db* tdbb, Firebird::string& plan, - bool detailed, unsigned level) const = 0; + bool detailed, unsigned level, bool recurse) const = 0; virtual void markRecursive() = 0; virtual void invalidateRecords(jrd_req* request) const = 0; @@ -86,6 +86,20 @@ namespace Jrd return true; } + ULONG getCursorProfileId() const + { + return m_cursorProfileId; + } + + ULONG getRecSourceProfileId() const + { + return m_recSourceProfileId; + } + + void open(thread_db* tdbb) const; + + bool getRecord(thread_db* tdbb) const; + protected: // Generic impure block struct Impure @@ -99,9 +113,7 @@ namespace Jrd static const ULONG irsb_mustread = 8; static const ULONG irsb_singular_processed = 16; - RecordSource() - : m_impure(0), m_recursive(false) - {} + RecordSource(CompilerScratch* csb); static Firebird::string printName(thread_db* tdbb, const Firebird::string& name, bool quote = true); static Firebird::string printName(thread_db* tdbb, const Firebird::string& name, @@ -115,7 +127,12 @@ namespace Jrd static void saveRecord(thread_db* tdbb, record_param* rpb); static void restoreRecord(thread_db* tdbb, record_param* rpb); + virtual void internalOpen(thread_db* tdbb) const = 0; + virtual bool internalGetRecord(thread_db* tdbb) const = 0; + ULONG m_impure; + ULONG m_cursorProfileId; + ULONG m_recSourceProfileId; bool m_recursive; }; @@ -157,13 +174,16 @@ namespace Jrd StreamType stream, jrd_rel* relation, const Firebird::Array& dbkeyRanges); - void open(thread_db* tdbb) const override; void close(thread_db* tdbb) const override; - bool getRecord(thread_db* tdbb) const override; + void getChildren(Firebird::Array& children) const override; void print(thread_db* tdbb, Firebird::string& plan, - bool detailed, unsigned level) const override; + bool detailed, unsigned level, bool recurse) const override; + + protected: + void internalOpen(thread_db* tdbb) const override; + bool internalGetRecord(thread_db* tdbb) const override; private: const Firebird::string m_alias; @@ -182,13 +202,16 @@ namespace Jrd BitmapTableScan(CompilerScratch* csb, const Firebird::string& alias, StreamType stream, jrd_rel* relation, InversionNode* inversion); - void open(thread_db* tdbb) const override; void close(thread_db* tdbb) const override; - bool getRecord(thread_db* tdbb) const override; + void getChildren(Firebird::Array& children) const override; void print(thread_db* tdbb, Firebird::string& plan, - bool detailed, unsigned level) const override; + bool detailed, unsigned level, bool recurse) const override; + + protected: + void internalOpen(thread_db* tdbb) const override; + bool internalGetRecord(thread_db* tdbb) const override; private: const Firebird::string m_alias; @@ -217,13 +240,12 @@ namespace Jrd StreamType stream, jrd_rel* relation, InversionNode* index, USHORT keyLength); - void open(thread_db* tdbb) const override; void close(thread_db* tdbb) const override; - bool getRecord(thread_db* tdbb) const override; + void getChildren(Firebird::Array& children) const override; void print(thread_db* tdbb, Firebird::string& plan, - bool detailed, unsigned level) const override; + bool detailed, unsigned level, bool recurse) const override; void setInversion(InversionNode* inversion, BoolExprNode* condition) { @@ -232,6 +254,10 @@ namespace Jrd m_condition = condition; } + protected: + void internalOpen(thread_db* tdbb) const override; + bool internalGetRecord(thread_db* tdbb) const override; + private: int compareKeys(const index_desc*, const UCHAR*, USHORT, const temporary_key*, USHORT) const; bool findSavedNode(thread_db* tdbb, Impure* impure, win* window, UCHAR**) const; @@ -262,15 +288,19 @@ namespace Jrd ExternalTableScan(CompilerScratch* csb, const Firebird::string& alias, StreamType stream, jrd_rel* relation); - void open(thread_db* tdbb) const override; void close(thread_db* tdbb) const override; - bool getRecord(thread_db* tdbb) const override; bool refetchRecord(thread_db* tdbb) const override; bool lockRecord(thread_db* tdbb) const override; + void getChildren(Firebird::Array& children) const override; + void print(thread_db* tdbb, Firebird::string& plan, - bool detailed, unsigned level) const override; + bool detailed, unsigned level, bool recurse) const override; + + protected: + void internalOpen(thread_db* tdbb) const override; + bool internalGetRecord(thread_db* tdbb) const override; private: jrd_rel* const m_relation; @@ -283,17 +313,20 @@ namespace Jrd VirtualTableScan(CompilerScratch* csb, const Firebird::string& alias, StreamType stream, jrd_rel* relation); - void open(thread_db* tdbb) const override; void close(thread_db* tdbb) const override; - bool getRecord(thread_db* tdbb) const override; bool refetchRecord(thread_db* tdbb) const override; bool lockRecord(thread_db* tdbb) const override; + void getChildren(Firebird::Array& children) const override; + void print(thread_db* tdbb, Firebird::string& plan, - bool detailed, unsigned level) const override; + bool detailed, unsigned level, bool recurse) const override; protected: + void internalOpen(thread_db* tdbb) const override; + bool internalGetRecord(thread_db* tdbb) const override; + virtual const Format* getFormat(thread_db* tdbb, jrd_rel* relation) const = 0; virtual bool retrieveRecord(thread_db* tdbb, jrd_rel* relation, FB_UINT64 position, Record* record) const = 0; @@ -316,15 +349,19 @@ namespace Jrd const jrd_prc* procedure, const ValueListNode* sourceList, const ValueListNode* targetList, MessageNode* message); - void open(thread_db* tdbb) const override; void close(thread_db* tdbb) const override; - bool getRecord(thread_db* tdbb) const override; bool refetchRecord(thread_db* tdbb) const override; bool lockRecord(thread_db* tdbb) const override; + void getChildren(Firebird::Array& children) const override; + void print(thread_db* tdbb, Firebird::string& plan, - bool detailed, unsigned level) const override; + bool detailed, unsigned level, bool recurse) const override; + + protected: + void internalOpen(thread_db* tdbb) const override; + bool internalGetRecord(thread_db* tdbb) const override; private: void assignParams(thread_db* tdbb, const dsc* from_desc, const dsc* flag_desc, @@ -345,15 +382,15 @@ namespace Jrd public: SingularStream(CompilerScratch* csb, RecordSource* next); - void open(thread_db* tdbb) const override; void close(thread_db* tdbb) const override; - bool getRecord(thread_db* tdbb) const override; bool refetchRecord(thread_db* tdbb) const override; bool lockRecord(thread_db* tdbb) const override; + void getChildren(Firebird::Array& children) const override; + void print(thread_db* tdbb, Firebird::string& plan, - bool detailed, unsigned level) const override; + bool detailed, unsigned level, bool recurse) const override; void markRecursive() override; void invalidateRecords(jrd_req* request) const override; @@ -361,9 +398,11 @@ namespace Jrd void findUsedStreams(StreamList& streams, bool expandAll = false) const override; void nullRecords(thread_db* tdbb) const override; - private: - void doGetRecord(thread_db* tdbb) const; + protected: + void internalOpen(thread_db* tdbb) const override; + bool internalGetRecord(thread_db* tdbb) const override; + private: NestConst m_next; StreamList m_streams; }; @@ -373,15 +412,15 @@ namespace Jrd public: LockedStream(CompilerScratch* csb, RecordSource* next); - void open(thread_db* tdbb) const override; void close(thread_db* tdbb) const override; - bool getRecord(thread_db* tdbb) const override; bool refetchRecord(thread_db* tdbb) const override; bool lockRecord(thread_db* tdbb) const override; + void getChildren(Firebird::Array& children) const override; + void print(thread_db* tdbb, Firebird::string& plan, - bool detailed, unsigned level) const override; + bool detailed, unsigned level, bool recurse) const override; void markRecursive() override; void invalidateRecords(jrd_req* request) const override; @@ -389,6 +428,10 @@ namespace Jrd void findUsedStreams(StreamList& streams, bool expandAll = false) const override; void nullRecords(thread_db* tdbb) const override; + protected: + void internalOpen(thread_db* tdbb) const override; + bool internalGetRecord(thread_db* tdbb) const override; + private: NestConst m_next; }; @@ -403,15 +446,15 @@ namespace Jrd public: FirstRowsStream(CompilerScratch* csb, RecordSource* next, ValueExprNode* value); - void open(thread_db* tdbb) const override; void close(thread_db* tdbb) const override; - bool getRecord(thread_db* tdbb) const override; bool refetchRecord(thread_db* tdbb) const override; bool lockRecord(thread_db* tdbb) const override; + void getChildren(Firebird::Array& children) const override; + void print(thread_db* tdbb, Firebird::string& plan, - bool detailed, unsigned level) const override; + bool detailed, unsigned level, bool recurse) const override; void markRecursive() override; void invalidateRecords(jrd_req* request) const override; @@ -424,6 +467,10 @@ namespace Jrd m_next->setAnyBoolean(anyBoolean, ansiAny, ansiNot); } + protected: + void internalOpen(thread_db* tdbb) const override; + bool internalGetRecord(thread_db* tdbb) const override; + private: NestConst m_next; NestConst const m_value; @@ -439,15 +486,15 @@ namespace Jrd public: SkipRowsStream(CompilerScratch* csb, RecordSource* next, ValueExprNode* value); - void open(thread_db* tdbb) const override; void close(thread_db* tdbb) const override; - bool getRecord(thread_db* tdbb) const override; bool refetchRecord(thread_db* tdbb) const override; bool lockRecord(thread_db* tdbb) const override; + void getChildren(Firebird::Array& children) const override; + void print(thread_db* tdbb, Firebird::string& plan, - bool detailed, unsigned level) const override; + bool detailed, unsigned level, bool recurse) const override; void markRecursive() override; void invalidateRecords(jrd_req* request) const override; @@ -460,6 +507,10 @@ namespace Jrd m_next->setAnyBoolean(anyBoolean, ansiAny, ansiNot); } + protected: + void internalOpen(thread_db* tdbb) const override; + bool internalGetRecord(thread_db* tdbb) const override; + private: NestConst m_next; NestConst const m_value; @@ -470,15 +521,15 @@ namespace Jrd public: FilteredStream(CompilerScratch* csb, RecordSource* next, BoolExprNode* boolean); - void open(thread_db* tdbb) const override; void close(thread_db* tdbb) const override; - bool getRecord(thread_db* tdbb) const override; bool refetchRecord(thread_db* tdbb) const override; bool lockRecord(thread_db* tdbb) const override; + void getChildren(Firebird::Array& children) const override; + void print(thread_db* tdbb, Firebird::string& plan, - bool detailed, unsigned level) const override; + bool detailed, unsigned level, bool recurse) const override; void markRecursive() override; void invalidateRecords(jrd_req* request) const override; @@ -496,6 +547,10 @@ namespace Jrd m_ansiNot = ansiNot; } + protected: + void internalOpen(thread_db* tdbb) const override; + bool internalGetRecord(thread_db* tdbb) const override; + private: bool evaluateBoolean(thread_db* tdbb) const; @@ -565,15 +620,15 @@ namespace Jrd SortedStream(CompilerScratch* csb, RecordSource* next, SortMap* map); - void open(thread_db* tdbb) const override; void close(thread_db* tdbb) const override; - bool getRecord(thread_db* tdbb) const override; bool refetchRecord(thread_db* tdbb) const override; bool lockRecord(thread_db* tdbb) const override; + void getChildren(Firebird::Array& children) const override; + void print(thread_db* tdbb, Firebird::string& plan, - bool detailed, unsigned level) const override; + bool detailed, unsigned level, bool recurse) const override; void markRecursive() override; void invalidateRecords(jrd_req* request) const override; @@ -614,6 +669,10 @@ namespace Jrd return (IS_INTL_DATA(desc) || desc->isDecFloat() || desc->isDateTimeTz()); } + protected: + void internalOpen(thread_db* tdbb) const override; + bool internalGetRecord(thread_db* tdbb) const override; + private: Sort* init(thread_db* tdbb) const; @@ -708,7 +767,6 @@ namespace Jrd const NestValueArray* group, MapNode* groupMap, bool oneRowWhenEmpty, NextType* next); public: - void open(thread_db* tdbb) const override; void close(thread_db* tdbb) const override; bool refetchRecord(thread_db* tdbb) const override; @@ -720,6 +778,8 @@ namespace Jrd void findUsedStreams(StreamList& streams, bool expandAll = false) const override; protected: + void internalOpen(thread_db* tdbb) const override; + Impure* getImpure(jrd_req* request) const { return request->getImpure(m_impure); @@ -782,8 +842,12 @@ namespace Jrd const NestValueArray* group, MapNode* map, RecordSource* next); public: - void print(thread_db* tdbb, Firebird::string& plan, bool detailed, unsigned level) const; - bool getRecord(thread_db* tdbb) const; + void getChildren(Firebird::Array& children) const override; + void print(thread_db* tdbb, Firebird::string& plan, bool detailed, unsigned level, bool recurse) const override; + + protected: + bool internalGetRecord(thread_db* tdbb) const override; + }; class WindowedStream : public RecordSource @@ -846,16 +910,18 @@ namespace Jrd Exclusion exclusion); public: - void open(thread_db* tdbb) const; - void close(thread_db* tdbb) const; + void close(thread_db* tdbb) const override; - bool getRecord(thread_db* tdbb) const; + void getChildren(Firebird::Array& children) const override; - void print(thread_db* tdbb, Firebird::string& plan, bool detailed, unsigned level) const; - void findUsedStreams(StreamList& streams, bool expandAll = false) const; - void nullRecords(thread_db* tdbb) const; + void print(thread_db* tdbb, Firebird::string& plan, bool detailed, unsigned level, bool recurse) const override; + void findUsedStreams(StreamList& streams, bool expandAll = false) const override; + void nullRecords(thread_db* tdbb) const override; protected: + void internalOpen(thread_db* tdbb) const override; + bool internalGetRecord(thread_db* tdbb) const override; + Impure* getImpure(jrd_req* request) const { return request->getImpure(m_impure); @@ -883,15 +949,15 @@ namespace Jrd WindowedStream(thread_db* tdbb, CompilerScratch* csb, Firebird::ObjectsArray& windows, RecordSource* next); - void open(thread_db* tdbb) const override; void close(thread_db* tdbb) const override; - bool getRecord(thread_db* tdbb) const override; bool refetchRecord(thread_db* tdbb) const override; bool lockRecord(thread_db* tdbb) const override; + void getChildren(Firebird::Array& children) const override; + void print(thread_db* tdbb, Firebird::string& plan, - bool detailed, unsigned level) const override; + bool detailed, unsigned level, bool recurse) const override; void markRecursive() override; void invalidateRecords(jrd_req* request) const override; @@ -899,6 +965,10 @@ namespace Jrd void findUsedStreams(StreamList& streams, bool expandAll = false) const override; void nullRecords(thread_db* tdbb) const override; + protected: + void internalOpen(thread_db* tdbb) const override; + bool internalGetRecord(thread_db* tdbb) const override; + private: NestConst m_next; NestConst m_joinedStream; @@ -908,6 +978,10 @@ namespace Jrd class BaseBufferedStream : public RecordSource { public: + BaseBufferedStream(CompilerScratch* csb) + : RecordSource(csb) + {} + virtual void locate(thread_db* tdbb, FB_UINT64 position) const = 0; virtual FB_UINT64 getCount(thread_db* tdbb) const = 0; virtual FB_UINT64 getPosition(jrd_req* request) const = 0; @@ -943,15 +1017,15 @@ namespace Jrd public: BufferedStream(CompilerScratch* csb, RecordSource* next); - void open(thread_db* tdbb) const override; void close(thread_db* tdbb) const override; - bool getRecord(thread_db* tdbb) const override; bool refetchRecord(thread_db* tdbb) const override; bool lockRecord(thread_db* tdbb) const override; + void getChildren(Firebird::Array& children) const override; + void print(thread_db* tdbb, Firebird::string& plan, - bool detailed, unsigned level) const override; + bool detailed, unsigned level, bool recurse) const override; void markRecursive() override; void invalidateRecords(jrd_req* request) const override; @@ -968,6 +1042,10 @@ namespace Jrd return impure->irsb_position; } + protected: + void internalOpen(thread_db* tdbb) const override; + bool internalGetRecord(thread_db* tdbb) const override; + private: NestConst m_next; Firebird::HalfStaticArray m_map; @@ -983,15 +1061,15 @@ namespace Jrd NestedLoopJoin(CompilerScratch* csb, RecordSource* outer, RecordSource* inner, BoolExprNode* boolean, JoinType joinType); - void open(thread_db* tdbb) const override; void close(thread_db* tdbb) const override; - bool getRecord(thread_db* tdbb) const override; bool refetchRecord(thread_db* tdbb) const override; bool lockRecord(thread_db* tdbb) const override; + void getChildren(Firebird::Array& children) const override; + void print(thread_db* tdbb, Firebird::string& plan, - bool detailed, unsigned level) const override; + bool detailed, unsigned level, bool recurse) const override; void markRecursive() override; void invalidateRecords(jrd_req* request) const override; @@ -999,6 +1077,10 @@ namespace Jrd void findUsedStreams(StreamList& streams, bool expandAll = false) const override; void nullRecords(thread_db* tdbb) const override; + protected: + void internalOpen(thread_db* tdbb) const override; + bool internalGetRecord(thread_db* tdbb) const override; + private: bool fetchRecord(thread_db*, FB_SIZE_T) const; @@ -1012,15 +1094,15 @@ namespace Jrd public: FullOuterJoin(CompilerScratch* csb, RecordSource* arg1, RecordSource* arg2); - void open(thread_db* tdbb) const override; void close(thread_db* tdbb) const override; - bool getRecord(thread_db* tdbb) const override; bool refetchRecord(thread_db* tdbb) const override; bool lockRecord(thread_db* tdbb) const override; + void getChildren(Firebird::Array& children) const override; + void print(thread_db* tdbb, Firebird::string& plan, - bool detailed, unsigned level) const override; + bool detailed, unsigned level, bool recurse) const override; void markRecursive() override; void invalidateRecords(jrd_req* request) const override; @@ -1028,6 +1110,10 @@ namespace Jrd void findUsedStreams(StreamList& streams, bool expandAll = false) const override; void nullRecords(thread_db* tdbb) const override; + protected: + void internalOpen(thread_db* tdbb) const override; + bool internalGetRecord(thread_db* tdbb) const override; + private: NestConst m_arg1; NestConst m_arg2; @@ -1061,15 +1147,15 @@ namespace Jrd HashJoin(thread_db* tdbb, CompilerScratch* csb, FB_SIZE_T count, RecordSource* const* args, NestValueArray* const* keys); - void open(thread_db* tdbb) const override; void close(thread_db* tdbb) const override; - bool getRecord(thread_db* tdbb) const override; bool refetchRecord(thread_db* tdbb) const override; bool lockRecord(thread_db* tdbb) const override; + void getChildren(Firebird::Array& children) const override; + void print(thread_db* tdbb, Firebird::string& plan, - bool detailed, unsigned level) const override; + bool detailed, unsigned level, bool recurse) const override; void markRecursive() override; void invalidateRecords(jrd_req* request) const override; @@ -1077,6 +1163,10 @@ namespace Jrd void findUsedStreams(StreamList& streams, bool expandAll = false) const override; void nullRecords(thread_db* tdbb) const override; + protected: + void internalOpen(thread_db* tdbb) const override; + bool internalGetRecord(thread_db* tdbb) const override; + private: ULONG computeHash(thread_db* tdbb, jrd_req* request, const SubStream& sub, UCHAR* buffer) const; @@ -1121,15 +1211,15 @@ namespace Jrd SortedStream* const* args, const NestValueArray* const* keys); - void open(thread_db* tdbb) const override; void close(thread_db* tdbb) const override; - bool getRecord(thread_db* tdbb) const override; bool refetchRecord(thread_db* tdbb) const override; bool lockRecord(thread_db* tdbb) const override; + void getChildren(Firebird::Array& children) const override; + void print(thread_db* tdbb, Firebird::string& plan, - bool detailed, unsigned level) const override; + bool detailed, unsigned level, bool recurse) const override; void markRecursive() override; void invalidateRecords(jrd_req* request) const override; @@ -1137,11 +1227,15 @@ namespace Jrd void findUsedStreams(StreamList& streams, bool expandAll = false) const override; void nullRecords(thread_db* tdbb) const override; + protected: + void internalOpen(thread_db* tdbb) const override; + bool internalGetRecord(thread_db* tdbb) const override; + private: int compare(thread_db* tdbb, const NestValueArray* node1, const NestValueArray* node2) const; UCHAR* getData(thread_db* tdbb, MergeFile* mfb, SLONG record) const; - SLONG getRecord(thread_db* tdbb, FB_SIZE_T index) const; + SLONG getRecordByIndex(thread_db* tdbb, FB_SIZE_T index) const; bool fetchRecord(thread_db* tdbb, FB_SIZE_T index) const; Firebird::Array > m_args; @@ -1153,14 +1247,19 @@ namespace Jrd public: LocalTableStream(CompilerScratch* csb, StreamType stream, const DeclareLocalTableNode* table); - void open(thread_db* tdbb) const override; void close(thread_db* tdbb) const override; - bool getRecord(thread_db* tdbb) const override; + void getChildren(Firebird::Array& children) const override; + bool refetchRecord(thread_db* tdbb) const override; bool lockRecord(thread_db* tdbb) const override; - void print(thread_db* tdbb, Firebird::string& plan, bool detailed, unsigned level) const override; + void print(thread_db* tdbb, Firebird::string& plan, + bool detailed, unsigned level, bool recurse) const override; + + protected: + void internalOpen(thread_db* tdbb) const override; + bool internalGetRecord(thread_db* tdbb) const override; private: const DeclareLocalTableNode* m_table; @@ -1178,20 +1277,24 @@ namespace Jrd FB_SIZE_T argCount, RecordSource* const* args, NestConst* maps, FB_SIZE_T streamCount, const StreamType* streams); - void open(thread_db* tdbb) const override; void close(thread_db* tdbb) const override; - bool getRecord(thread_db* tdbb) const override; bool refetchRecord(thread_db* tdbb) const override; bool lockRecord(thread_db* tdbb) const override; + void getChildren(Firebird::Array& children) const override; + void print(thread_db* tdbb, Firebird::string& plan, - bool detailed, unsigned level) const override; + bool detailed, unsigned level, bool recurse) const override; void markRecursive() override; void invalidateRecords(jrd_req* request) const override; void findUsedStreams(StreamList& streams, bool expandAll = false) const override; + protected: + void internalOpen(thread_db* tdbb) const override; + bool internalGetRecord(thread_db* tdbb) const override; + private: Firebird::Array > m_args; Firebird::Array > m_maps; @@ -1219,20 +1322,24 @@ namespace Jrd FB_SIZE_T streamCount, const StreamType* innerStreams, ULONG saveOffset); - void open(thread_db* tdbb) const override; void close(thread_db* tdbb) const override; - bool getRecord(thread_db* tdbb) const override; bool refetchRecord(thread_db* tdbb) const override; bool lockRecord(thread_db* tdbb) const override; + void getChildren(Firebird::Array& children) const override; + void print(thread_db* tdbb, Firebird::string& plan, - bool detailed, unsigned level) const override; + bool detailed, unsigned level, bool recurse) const override; void markRecursive() override; void invalidateRecords(jrd_req* request) const override; void findUsedStreams(StreamList& streams, bool expandAll = false) const override; + protected: + void internalOpen(thread_db* tdbb) const override; + bool internalGetRecord(thread_db* tdbb) const override; + private: void cleanupLevel(jrd_req* request, Impure* impure) const; @@ -1257,15 +1364,15 @@ namespace Jrd ConditionalStream(CompilerScratch* csb, RecordSource* first, RecordSource* second, BoolExprNode* boolean); - void open(thread_db* tdbb) const override; void close(thread_db* tdbb) const override; - bool getRecord(thread_db* tdbb) const override; bool refetchRecord(thread_db* tdbb) const override; bool lockRecord(thread_db* tdbb) const override; + void getChildren(Firebird::Array& children) const override; + void print(thread_db* tdbb, Firebird::string& plan, - bool detailed, unsigned level) const override; + bool detailed, unsigned level, bool recurse) const override; void markRecursive() override; void invalidateRecords(jrd_req* request) const override; @@ -1273,6 +1380,10 @@ namespace Jrd void findUsedStreams(StreamList& streams, bool expandAll = false) const override; void nullRecords(thread_db* tdbb) const override; + protected: + void internalOpen(thread_db* tdbb) const override; + bool internalGetRecord(thread_db* tdbb) const override; + private: NestConst m_first; NestConst m_second; diff --git a/src/jrd/recsrc/RecursiveStream.cpp b/src/jrd/recsrc/RecursiveStream.cpp index f6339df6f9..52c0e302bd 100644 --- a/src/jrd/recsrc/RecursiveStream.cpp +++ b/src/jrd/recsrc/RecursiveStream.cpp @@ -66,7 +66,7 @@ RecursiveStream::RecursiveStream(CompilerScratch* csb, StreamType stream, Stream m_inner->markRecursive(); } -void RecursiveStream::open(thread_db* tdbb) const +void RecursiveStream::internalOpen(thread_db* tdbb) const { jrd_req* const request = tdbb->getRequest(); Impure* const impure = request->getImpure(m_impure); @@ -115,7 +115,7 @@ void RecursiveStream::close(thread_db* tdbb) const } } -bool RecursiveStream::getRecord(thread_db* tdbb) const +bool RecursiveStream::internalGetRecord(thread_db* tdbb) const { JRD_reschedule(tdbb); @@ -243,24 +243,34 @@ bool RecursiveStream::lockRecord(thread_db* /*tdbb*/) const return false; // compiler silencer } -void RecursiveStream::print(thread_db* tdbb, string& plan, bool detailed, unsigned level) const +void RecursiveStream::getChildren(Array& children) const +{ + children.add(m_root); + children.add(m_inner); +} + +void RecursiveStream::print(thread_db* tdbb, string& plan, bool detailed, unsigned level, bool recurse) const { if (detailed) { plan += printIndent(++level) + "Recursion"; - m_root->print(tdbb, plan, true, level); - m_inner->print(tdbb, plan, true, level); + + if (recurse) + { + m_root->print(tdbb, plan, true, level, recurse); + m_inner->print(tdbb, plan, true, level, recurse); + } } else { if (!level) plan += "("; - m_root->print(tdbb, plan, false, level + 1); + m_root->print(tdbb, plan, false, level + 1, recurse); plan += ", "; - m_inner->print(tdbb, plan, false, level + 1); + m_inner->print(tdbb, plan, false, level + 1, recurse); if (!level) plan += ")"; diff --git a/src/jrd/recsrc/SingularStream.cpp b/src/jrd/recsrc/SingularStream.cpp index 45e3f0b03e..f3e9ff77a0 100644 --- a/src/jrd/recsrc/SingularStream.cpp +++ b/src/jrd/recsrc/SingularStream.cpp @@ -33,7 +33,9 @@ using namespace Jrd; // ------------------------------ SingularStream::SingularStream(CompilerScratch* csb, RecordSource* next) - : m_next(next), m_streams(csb->csb_pool) + : RecordSource(csb), + m_next(next), + m_streams(csb->csb_pool) { fb_assert(m_next); @@ -42,7 +44,7 @@ SingularStream::SingularStream(CompilerScratch* csb, RecordSource* next) m_impure = csb->allocImpure(); } -void SingularStream::open(thread_db* tdbb) const +void SingularStream::internalOpen(thread_db* tdbb) const { jrd_req* const request = tdbb->getRequest(); Impure* const impure = request->getImpure(m_impure); @@ -68,7 +70,7 @@ void SingularStream::close(thread_db* tdbb) const } } -bool SingularStream::getRecord(thread_db* tdbb) const +bool SingularStream::internalGetRecord(thread_db* tdbb) const { JRD_reschedule(tdbb); @@ -83,55 +85,48 @@ bool SingularStream::getRecord(thread_db* tdbb) const if (m_next->getRecord(tdbb)) { - doGetRecord(tdbb); + const FB_SIZE_T streamCount = m_streams.getCount(); + MemoryPool& pool = *tdbb->getDefaultPool(); + HalfStaticArray rpbs(pool, streamCount); + + for (FB_SIZE_T i = 0; i < streamCount; i++) + { + rpbs.add(request->req_rpb[m_streams[i]]); + record_param& rpb = rpbs.back(); + Record* const orgRecord = rpb.rpb_record; + + if (orgRecord) + rpb.rpb_record = FB_NEW_POOL(pool) Record(pool, orgRecord); + } + + if (m_next->getRecord(tdbb)) + status_exception::raise(Arg::Gds(isc_sing_select_err)); + + for (FB_SIZE_T i = 0; i < streamCount; i++) + { + record_param& rpb = request->req_rpb[m_streams[i]]; + Record* orgRecord = rpb.rpb_record; + rpb = rpbs[i]; + const AutoPtr newRecord(rpb.rpb_record); + + if (newRecord) + { + if (!orgRecord) + BUGCHECK(284); // msg 284 cannot restore singleton select data + + rpb.rpb_record = orgRecord; + orgRecord->copyFrom(newRecord); + } + } + + impure->irsb_flags |= irsb_singular_processed; + return true; } return false; } -void SingularStream::doGetRecord(thread_db* tdbb) const -{ - jrd_req* const request = tdbb->getRequest(); - Impure* const impure = request->getImpure(m_impure); - - const FB_SIZE_T streamCount = m_streams.getCount(); - MemoryPool& pool = *tdbb->getDefaultPool(); - HalfStaticArray rpbs(pool, streamCount); - - for (FB_SIZE_T i = 0; i < streamCount; i++) - { - rpbs.add(request->req_rpb[m_streams[i]]); - record_param& rpb = rpbs.back(); - Record* const orgRecord = rpb.rpb_record; - - if (orgRecord) - rpb.rpb_record = FB_NEW_POOL(pool) Record(pool, orgRecord); - } - - if (m_next->getRecord(tdbb)) - status_exception::raise(Arg::Gds(isc_sing_select_err)); - - for (FB_SIZE_T i = 0; i < streamCount; i++) - { - record_param& rpb = request->req_rpb[m_streams[i]]; - Record* orgRecord = rpb.rpb_record; - rpb = rpbs[i]; - const AutoPtr newRecord(rpb.rpb_record); - - if (newRecord) - { - if (!orgRecord) - BUGCHECK(284); // msg 284 cannot restore singleton select data - - rpb.rpb_record = orgRecord; - orgRecord->copyFrom(newRecord); - } - } - - impure->irsb_flags |= irsb_singular_processed; -} - bool SingularStream::refetchRecord(thread_db* tdbb) const { return m_next->refetchRecord(tdbb); @@ -142,12 +137,18 @@ bool SingularStream::lockRecord(thread_db* tdbb) const return m_next->lockRecord(tdbb); } -void SingularStream::print(thread_db* tdbb, string& plan, bool detailed, unsigned level) const +void SingularStream::getChildren(Array& children) const +{ + children.add(m_next); +} + +void SingularStream::print(thread_db* tdbb, string& plan, bool detailed, unsigned level, bool recurse) const { if (detailed) plan += printIndent(++level) + "Singularity Check"; - m_next->print(tdbb, plan, detailed, level); + if (recurse) + m_next->print(tdbb, plan, detailed, level, recurse); } void SingularStream::markRecursive() diff --git a/src/jrd/recsrc/SkipRowsStream.cpp b/src/jrd/recsrc/SkipRowsStream.cpp index 6a22ea099f..05619513b4 100644 --- a/src/jrd/recsrc/SkipRowsStream.cpp +++ b/src/jrd/recsrc/SkipRowsStream.cpp @@ -37,14 +37,16 @@ using namespace Jrd; // ------------------------------- SkipRowsStream::SkipRowsStream(CompilerScratch* csb, RecordSource* next, ValueExprNode* value) - : m_next(next), m_value(value) + : RecordSource(csb), + m_next(next), + m_value(value) { fb_assert(m_next && m_value); m_impure = csb->allocImpure(); } -void SkipRowsStream::open(thread_db* tdbb) const +void SkipRowsStream::internalOpen(thread_db* tdbb) const { jrd_req* const request = tdbb->getRequest(); Impure* const impure = request->getImpure(m_impure); @@ -80,7 +82,7 @@ void SkipRowsStream::close(thread_db* tdbb) const } } -bool SkipRowsStream::getRecord(thread_db* tdbb) const +bool SkipRowsStream::internalGetRecord(thread_db* tdbb) const { JRD_reschedule(tdbb); @@ -113,12 +115,18 @@ bool SkipRowsStream::lockRecord(thread_db* tdbb) const return m_next->lockRecord(tdbb); } -void SkipRowsStream::print(thread_db* tdbb, string& plan, bool detailed, unsigned level) const +void SkipRowsStream::getChildren(Array& children) const +{ + children.add(m_next); +} + +void SkipRowsStream::print(thread_db* tdbb, string& plan, bool detailed, unsigned level, bool recurse) const { if (detailed) plan += printIndent(++level) + "Skip N Records"; - m_next->print(tdbb, plan, detailed, level); + if (recurse) + m_next->print(tdbb, plan, detailed, level, recurse); } void SkipRowsStream::markRecursive() diff --git a/src/jrd/recsrc/SortedStream.cpp b/src/jrd/recsrc/SortedStream.cpp index 895954af8d..530ca29e5d 100644 --- a/src/jrd/recsrc/SortedStream.cpp +++ b/src/jrd/recsrc/SortedStream.cpp @@ -43,14 +43,16 @@ using namespace Jrd; // ----------------------------- SortedStream::SortedStream(CompilerScratch* csb, RecordSource* next, SortMap* map) - : m_next(next), m_map(map) + : RecordSource(csb), + m_next(next), + m_map(map) { fb_assert(m_next && m_map); m_impure = csb->allocImpure(); } -void SortedStream::open(thread_db* tdbb) const +void SortedStream::internalOpen(thread_db* tdbb) const { jrd_req* const request = tdbb->getRequest(); Impure* const impure = request->getImpure(m_impure); @@ -84,7 +86,7 @@ void SortedStream::close(thread_db* tdbb) const } } -bool SortedStream::getRecord(thread_db* tdbb) const +bool SortedStream::internalGetRecord(thread_db* tdbb) const { JRD_reschedule(tdbb); @@ -114,8 +116,13 @@ bool SortedStream::lockRecord(thread_db* tdbb) const return m_next->lockRecord(tdbb); } +void SortedStream::getChildren(Array& children) const +{ + children.add(m_next); +} + void SortedStream::print(thread_db* tdbb, string& plan, - bool detailed, unsigned level) const + bool detailed, unsigned level, bool recurse) const { if (detailed) { @@ -129,13 +136,14 @@ void SortedStream::print(thread_db* tdbb, string& plan, plan += printIndent(++level) + ((m_map->flags & FLAG_PROJECT) ? "Unique Sort" : "Sort") + extras; - m_next->print(tdbb, plan, true, level); + if (recurse) + m_next->print(tdbb, plan, true, level, recurse); } else { level++; plan += "SORT ("; - m_next->print(tdbb, plan, false, level); + m_next->print(tdbb, plan, false, level, recurse); plan += ")"; } } diff --git a/src/jrd/recsrc/Union.cpp b/src/jrd/recsrc/Union.cpp index 1f3692ea50..94c7f3d7e7 100644 --- a/src/jrd/recsrc/Union.cpp +++ b/src/jrd/recsrc/Union.cpp @@ -59,7 +59,7 @@ Union::Union(CompilerScratch* csb, StreamType stream, m_streams[i] = streams[i]; } -void Union::open(thread_db* tdbb) const +void Union::internalOpen(thread_db* tdbb) const { jrd_req* const request = tdbb->getRequest(); Impure* const impure = request->getImpure(m_impure); @@ -97,7 +97,7 @@ void Union::close(thread_db* tdbb) const } } -bool Union::getRecord(thread_db* tdbb) const +bool Union::internalGetRecord(thread_db* tdbb) const { JRD_reschedule(tdbb); @@ -164,14 +164,23 @@ bool Union::lockRecord(thread_db* tdbb) const return m_args[impure->irsb_count]->lockRecord(tdbb); } -void Union::print(thread_db* tdbb, string& plan, bool detailed, unsigned level) const +void Union::getChildren(Array& children) const +{ + for (FB_SIZE_T i = 0; i < m_args.getCount(); i++) + children.add(m_args[i]); +} + +void Union::print(thread_db* tdbb, string& plan, bool detailed, unsigned level, bool recurse) const { if (detailed) { plan += printIndent(++level) + (m_args.getCount() == 1 ? "Materialize" : "Union"); - for (FB_SIZE_T i = 0; i < m_args.getCount(); i++) - m_args[i]->print(tdbb, plan, true, level); + if (recurse) + { + for (FB_SIZE_T i = 0; i < m_args.getCount(); i++) + m_args[i]->print(tdbb, plan, true, level, recurse); + } } else { @@ -183,7 +192,7 @@ void Union::print(thread_db* tdbb, string& plan, bool detailed, unsigned level) if (i) plan += ", "; - m_args[i]->print(tdbb, plan, false, level + 1); + m_args[i]->print(tdbb, plan, false, level + 1, recurse); } if (!level) diff --git a/src/jrd/recsrc/VirtualTableScan.cpp b/src/jrd/recsrc/VirtualTableScan.cpp index 0cc73fe9d2..a53a08606e 100644 --- a/src/jrd/recsrc/VirtualTableScan.cpp +++ b/src/jrd/recsrc/VirtualTableScan.cpp @@ -44,7 +44,7 @@ VirtualTableScan::VirtualTableScan(CompilerScratch* csb, const string& alias, m_impure = csb->allocImpure(); } -void VirtualTableScan::open(thread_db* tdbb) const +void VirtualTableScan::internalOpen(thread_db* tdbb) const { jrd_req* const request = tdbb->getRequest(); Impure* const impure = request->getImpure(m_impure); @@ -71,7 +71,7 @@ void VirtualTableScan::close(thread_db* tdbb) const impure->irsb_flags &= ~irsb_open; } -bool VirtualTableScan::getRecord(thread_db* tdbb) const +bool VirtualTableScan::internalGetRecord(thread_db* tdbb) const { JRD_reschedule(tdbb); @@ -110,7 +110,11 @@ bool VirtualTableScan::lockRecord(thread_db* /*tdbb*/) const return false; // compiler silencer } -void VirtualTableScan::print(thread_db* tdbb, string& plan, bool detailed, unsigned level) const +void VirtualTableScan::getChildren(Array& children) const +{ +} + +void VirtualTableScan::print(thread_db* tdbb, string& plan, bool detailed, unsigned level, bool recurse) const { if (detailed) { diff --git a/src/jrd/recsrc/WindowedStream.cpp b/src/jrd/recsrc/WindowedStream.cpp index e4d041fe98..3da9674f22 100644 --- a/src/jrd/recsrc/WindowedStream.cpp +++ b/src/jrd/recsrc/WindowedStream.cpp @@ -50,34 +50,36 @@ namespace public: BufferedStreamWindow(CompilerScratch* csb, BufferedStream* next); - void open(thread_db* tdbb) const; - void close(thread_db* tdbb) const; + void internalOpen(thread_db* tdbb) const override; + void close(thread_db* tdbb) const override; - bool getRecord(thread_db* tdbb) const; - bool refetchRecord(thread_db* tdbb) const; - bool lockRecord(thread_db* tdbb) const; + bool internalGetRecord(thread_db* tdbb) const override; + bool refetchRecord(thread_db* tdbb) const override; + bool lockRecord(thread_db* tdbb) const override; - void print(thread_db* tdbb, Firebird::string& plan, bool detailed, unsigned level) const; + void getChildren(Firebird::Array& children) const override; - void markRecursive(); - void invalidateRecords(jrd_req* request) const; + void print(thread_db* tdbb, Firebird::string& plan, bool detailed, unsigned level, bool recurse) const override; - void findUsedStreams(StreamList& streams, bool expandAll) const; - void nullRecords(thread_db* tdbb) const; + void markRecursive() override; + void invalidateRecords(jrd_req* request) const override; - void locate(thread_db* tdbb, FB_UINT64 position) const + void findUsedStreams(StreamList& streams, bool expandAll) const override; + void nullRecords(thread_db* tdbb) const override; + + void locate(thread_db* tdbb, FB_UINT64 position) const override { jrd_req* const request = tdbb->getRequest(); Impure* const impure = request->getImpure(m_impure); impure->irsb_position = position; } - FB_UINT64 getCount(thread_db* tdbb) const + FB_UINT64 getCount(thread_db* tdbb) const override { return m_next->getCount(tdbb); } - FB_UINT64 getPosition(jrd_req* request) const + FB_UINT64 getPosition(jrd_req* request) const override { Impure* const impure = request->getImpure(m_impure); return impure->irsb_position; @@ -90,12 +92,13 @@ namespace // BufferedStreamWindow implementation BufferedStreamWindow::BufferedStreamWindow(CompilerScratch* csb, BufferedStream* next) - : m_next(next) + : BaseBufferedStream(csb), + m_next(next) { m_impure = csb->allocImpure(); } - void BufferedStreamWindow::open(thread_db* tdbb) const + void BufferedStreamWindow::internalOpen(thread_db* tdbb) const { jrd_req* const request = tdbb->getRequest(); Impure* const impure = request->getImpure(m_impure); @@ -116,7 +119,7 @@ namespace impure->irsb_flags &= ~irsb_open; } - bool BufferedStreamWindow::getRecord(thread_db* tdbb) const + bool BufferedStreamWindow::internalGetRecord(thread_db* tdbb) const { jrd_req* const request = tdbb->getRequest(); Impure* const impure = request->getImpure(m_impure); @@ -142,9 +145,15 @@ namespace return m_next->lockRecord(tdbb); } - void BufferedStreamWindow::print(thread_db* tdbb, string& plan, bool detailed, unsigned level) const + void BufferedStreamWindow::getChildren(Array& children) const { - m_next->print(tdbb, plan, detailed, level); + children.add(m_next); + } + + void BufferedStreamWindow::print(thread_db* tdbb, string& plan, bool detailed, unsigned level, bool recurse) const + { + if (recurse) + m_next->print(tdbb, plan, detailed, level, recurse); } void BufferedStreamWindow::markRecursive() @@ -184,7 +193,8 @@ namespace WindowedStream::WindowedStream(thread_db* tdbb, CompilerScratch* csb, ObjectsArray& windows, RecordSource* next) - : m_next(FB_NEW_POOL(csb->csb_pool) BufferedStream(csb, next)), + : RecordSource(csb), + m_next(FB_NEW_POOL(csb->csb_pool) BufferedStream(csb, next)), m_joinedStream(NULL) { m_impure = csb->allocImpure(); @@ -330,7 +340,7 @@ WindowedStream::WindowedStream(thread_db* tdbb, CompilerScratch* csb, } } -void WindowedStream::open(thread_db* tdbb) const +void WindowedStream::internalOpen(thread_db* tdbb) const { jrd_req* const request = tdbb->getRequest(); Impure* const impure = request->getImpure(m_impure); @@ -357,7 +367,7 @@ void WindowedStream::close(thread_db* tdbb) const } } -bool WindowedStream::getRecord(thread_db* tdbb) const +bool WindowedStream::internalGetRecord(thread_db* tdbb) const { JRD_reschedule(tdbb); @@ -384,9 +394,15 @@ bool WindowedStream::lockRecord(thread_db* /*tdbb*/) const return false; // compiler silencer } -void WindowedStream::print(thread_db* tdbb, string& plan, bool detailed, unsigned level) const +void WindowedStream::getChildren(Array& children) const { - m_joinedStream->print(tdbb, plan, detailed, level); + children.add(m_joinedStream); +} + +void WindowedStream::print(thread_db* tdbb, string& plan, bool detailed, unsigned level, bool recurse) const +{ + if (recurse) + m_joinedStream->print(tdbb, plan, detailed, level, recurse); } void WindowedStream::markRecursive() @@ -502,9 +518,9 @@ WindowedStream::WindowStream::WindowStream(thread_db* tdbb, CompilerScratch* csb (void) m_exclusion; // avoid warning } -void WindowedStream::WindowStream::open(thread_db* tdbb) const +void WindowedStream::WindowStream::internalOpen(thread_db* tdbb) const { - BaseAggWinStream::open(tdbb); + BaseAggWinStream::internalOpen(tdbb); jrd_req* const request = tdbb->getRequest(); Impure* const impure = getImpure(request); @@ -539,7 +555,7 @@ void WindowedStream::WindowStream::close(thread_db* tdbb) const BaseAggWinStream::close(tdbb); } -bool WindowedStream::WindowStream::getRecord(thread_db* tdbb) const +bool WindowedStream::WindowStream::internalGetRecord(thread_db* tdbb) const { JRD_reschedule(tdbb); @@ -867,13 +883,19 @@ bool WindowedStream::WindowStream::getRecord(thread_db* tdbb) const return true; } +void WindowedStream::WindowStream::getChildren(Array& children) const +{ + children.add(m_next); +} + void WindowedStream::WindowStream::print(thread_db* tdbb, string& plan, bool detailed, - unsigned level) const + unsigned level, bool recurse) const { if (detailed) plan += printIndent(++level) + "Window"; - m_next->print(tdbb, plan, detailed, level); + if (recurse) + m_next->print(tdbb, plan, detailed, level, recurse); } void WindowedStream::WindowStream::findUsedStreams(StreamList& streams, bool expandAll) const diff --git a/src/jrd/tra.cpp b/src/jrd/tra.cpp index f553242d85..2e7429e2e9 100644 --- a/src/jrd/tra.cpp +++ b/src/jrd/tra.cpp @@ -828,7 +828,7 @@ void TRA_invalidate(thread_db* tdbb, ULONG mask) Database* const database = tdbb->getDatabase(); - EngineCheckout cout(tdbb, FB_FUNCTION, true); + EngineCheckout cout(tdbb, FB_FUNCTION, EngineCheckout::UNNECESSARY); SyncLockGuard dbbSync(&database->dbb_sync, SYNC_SHARED, "TRA_invalidate"); diff --git a/src/jrd/vio.cpp b/src/jrd/vio.cpp index ce2ebb12d2..46b15bc3fa 100644 --- a/src/jrd/vio.cpp +++ b/src/jrd/vio.cpp @@ -88,6 +88,7 @@ #include "../jrd/Function.h" #include "../common/StatusArg.h" #include "../jrd/GarbageCollector.h" +#include "../jrd/ProfilerManager.h" #include "../jrd/trace/TraceManager.h" #include "../jrd/trace/TraceJrdHelpers.h" diff --git a/src/lock/lock.cpp b/src/lock/lock.cpp index 47f107a52e..a452d9053e 100644 --- a/src/lock/lock.cpp +++ b/src/lock/lock.cpp @@ -414,7 +414,7 @@ void LockManager::shutdownOwner(thread_db* tdbb, SRQ_PTR* owner_handle) { { // checkout scope LockTableCheckout checkout(this, FB_FUNCTION); - EngineCheckout cout(tdbb, FB_FUNCTION, true); + EngineCheckout cout(tdbb, FB_FUNCTION, EngineCheckout::UNNECESSARY); Thread::sleep(10); } @@ -1398,7 +1398,7 @@ void LockManager::blocking_action(thread_db* tdbb, SRQ_PTR blocking_owner_offset { // checkout scope LockTableCheckout checkout(this, FB_FUNCTION); - EngineCheckout cout(tdbb, FB_FUNCTION, true); + EngineCheckout cout(tdbb, FB_FUNCTION, EngineCheckout::UNNECESSARY); (*routine)(arg); } @@ -3826,7 +3826,7 @@ void LockManager::wait_for_request(thread_db* tdbb, lrq* request, SSHORT lck_wai } { // scope - EngineCheckout cout(tdbb, FB_FUNCTION, true); + EngineCheckout cout(tdbb, FB_FUNCTION, EngineCheckout::UNNECESSARY); ret = m_sharedMemory->eventWait(&owner->own_wakeup, value, (timeout - current_time) * 1000000); --m_waitingOwners; } diff --git a/src/plugins/profiler/Profiler.cpp b/src/plugins/profiler/Profiler.cpp new file mode 100644 index 0000000000..4b05e4de46 --- /dev/null +++ b/src/plugins/profiler/Profiler.cpp @@ -0,0 +1,1224 @@ +/* + * The contents of this file are subject to the Initial + * Developer's Public License Version 1.0 (the "License"); + * you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * http://www.ibphoenix.com/main.nfs?a=ibphoenix&page=ibp_idpl. + * + * Software distributed under the License is distributed AS IS, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. + * See the License for the specific language governing rights + * and limitations under the License. + * + * The Original Code was created by Adriano dos Santos Fernandes + * for the Firebird Open Source RDBMS project. + * + * Copyright (c) 2020 Adriano dos Santos Fernandes + * and all contributors signed below. + * + * All Rights Reserved. + * Contributor(s): ______________________________________. + */ + +#include "firebird.h" +#include "firebird/Message.h" +#include "../common/classes/ImplementHelper.h" +#include "../common/classes/auto.h" +#include "../common/classes/fb_pair.h" +#include "../common/classes/fb_string.h" +#include "../common/classes/GenericMap.h" +#include "../common/classes/MetaString.h" +#include "../common/classes/Nullable.h" +#include "../common/classes/objects_array.h" +#include "../common/classes/stack.h" +#include "../common/status.h" +#include "../intl/charsets.h" +#include + +using namespace Firebird; + + +namespace +{ + +class ProfilerPlugin; + +auto& defaultPool() +{ + return *getDefaultMemoryPool(); +} + +struct Stats +{ + void hit(FB_UINT64 runTime) + { + if (counter == 0 || runTime < minTime) + minTime = runTime; + + if (counter == 0 || runTime > maxTime) + maxTime = runTime; + + totalTime += runTime; + ++counter; + } + + FB_UINT64 counter = 0; + FB_UINT64 minTime = 0; + FB_UINT64 maxTime = 0; + FB_UINT64 totalTime = 0; +}; + +struct RecordSource +{ + Nullable parentId; + string accessPath{defaultPool()}; +}; + +struct RecordSourceStats +{ + Stats openStats; + Stats fetchStats; +}; + +struct Statement +{ + unsigned level = 0; + string type{defaultPool()}; + MetaString packageName{defaultPool()}; + MetaString routineName{defaultPool()}; + SINT64 parentStatementId; + string sqlText{defaultPool()}; +}; + +using LineColumnKey = NonPooledPair; +using CursorRecSourceKey = NonPooledPair; + +struct Request +{ + bool dirty = true; + unsigned level = 0; + SINT64 statementId; + SINT64 callerRequestId; + ISC_TIMESTAMP_TZ startTimestamp; + Nullable finishTimestamp; + NonPooledMap recordSourcesStats{defaultPool()}; + NonPooledMap psqlStats{defaultPool()}; +}; + +using StatementCursorRecSourceKey = NonPooledPair, unsigned>; + +class Session final : + public IProfilerSessionImpl, + public RefCounted +{ +public: + Session(ThrowStatusExceptionWrapper* status, ProfilerPlugin* aPlugin, ITransaction* transaction, + const char* aDescription, ISC_TIMESTAMP_TZ aStartTimestamp); + +public: + void dispose() override + { + plugin = nullptr; // avoid circular reference + release(); + } + +public: + SINT64 getId() override + { + return id; + } + + unsigned getFlags() override + { + return FLAG_AFTER_EVENTS; + } + + void finish(ThrowStatusExceptionWrapper* status, ISC_TIMESTAMP_TZ timestamp) override; + + void defineStatement(ThrowStatusExceptionWrapper* status, SINT64 statementId, SINT64 parentStatementId, + const char* type, const char* packageName, const char* routineName, const char* sqlText) override; + + void defineRecordSource(SINT64 statementId, unsigned cursorId, unsigned recSourceId, + const char* accessPath, unsigned parentRecordSourceId) override; + + void onRequestStart(ThrowStatusExceptionWrapper* status, SINT64 requestId, SINT64 statementId, + SINT64 callerRequestId, ISC_TIMESTAMP_TZ timestamp) override; + + void onRequestFinish(ThrowStatusExceptionWrapper* status, SINT64 requestId, ISC_TIMESTAMP_TZ timestamp) override; + + void beforePsqlLineColumn(SINT64 requestId, unsigned line, unsigned column) override + { + } + + void afterPsqlLineColumn(SINT64 requestId, unsigned line, unsigned column, FB_UINT64 runTime) override; + + void beforeRecordSourceOpen(SINT64 requestId, unsigned cursorId, unsigned recSourceId) override + { + } + + void afterRecordSourceOpen(SINT64 requestId, unsigned cursorId, unsigned recSourceId, FB_UINT64 runTime) override; + + void beforeRecordSourceGetRecord(SINT64 requestId, unsigned cursorId, unsigned recSourceId) override + { + } + + void afterRecordSourceGetRecord(SINT64 requestId, unsigned cursorId, unsigned recSourceId, + FB_UINT64 runTime) override; + +public: + RefPtr plugin; + NonPooledMap statements{defaultPool()}; + NonPooledMap recordSources{defaultPool()}; + NonPooledMap requests{defaultPool()}; + SINT64 id; + bool dirty = true; + ISC_TIMESTAMP_TZ startTimestamp; + Nullable finishTimestamp; + string description{defaultPool()}; +}; + +class ProfilerPlugin final : public StdPlugin> +{ +public: + explicit ProfilerPlugin(IPluginConfig*) + { + } + + void init(ThrowStatusExceptionWrapper* status, IAttachment* attachment, ITransaction* transaction) override; + + IProfilerSession* startSession(ThrowStatusExceptionWrapper* status, ITransaction* transaction, + const char* description, ISC_TIMESTAMP_TZ timestamp) override; + + void flush(ThrowStatusExceptionWrapper* status, ITransaction* transaction) override; + +private: + void createMetadata(ThrowStatusExceptionWrapper* status, RefPtr transaction); + void loadMetadata(ThrowStatusExceptionWrapper* status, RefPtr transaction); + +public: + RefPtr attachment; + ObjectsArray> sessions{getPool()}; +}; + +//-------------------------------------- + +void ProfilerPlugin::init(ThrowStatusExceptionWrapper* status, IAttachment* attachment, ITransaction* transaction) +{ + this->attachment = attachment; + + constexpr auto sql = R"""( + select coalesce( + (select true + from rdb$relations + where rdb$relation_name = 'FBPROF$SESSIONS' + ), + false + ) metadata_created + from rdb$database + )"""; + + FB_MESSAGE(message, ThrowStatusExceptionWrapper, + (FB_BOOLEAN, metadataCreated) + ) message(status, MasterInterfacePtr()); + message.clear(); + + RefPtr refTransaction; + + for (unsigned i = 0; i < 2; ++i) + { + auto resultSet = makeNoIncRef(attachment->openCursor(status, transaction, 0, sql, SQL_DIALECT_CURRENT, + nullptr, nullptr, message.getMetadata(), nullptr, 0)); + + if (resultSet->fetchNext(status, message.getData()) == IStatus::RESULT_NO_DATA) + { + fb_assert(false); + return; + } + + if (message->metadataCreated) + { + if (i == 1) + loadMetadata(status, std::move(refTransaction)); + + return; + } + else if (i == 0) + refTransaction = makeNoIncRef(transaction = attachment->startTransaction(status, 0, nullptr)); + } + + createMetadata(status, std::move(refTransaction)); +} + +IProfilerSession* ProfilerPlugin::startSession(ThrowStatusExceptionWrapper* status, ITransaction* transaction, + const char* description, ISC_TIMESTAMP_TZ timestamp) +{ + return FB_NEW Session(status, this, transaction, description, timestamp); +} + +void ProfilerPlugin::flush(ThrowStatusExceptionWrapper* status, ITransaction* transaction) +{ + constexpr auto sessionSql = R"""( + update or insert into fbprof$sessions + (session_id, attachment_id, user_name, description, start_timestamp, finish_timestamp) + values (?, current_connection, current_user, ?, ?, ?) + matching (session_id) + )"""; + + FB_MESSAGE(SessionMessage, ThrowStatusExceptionWrapper, + (FB_BIGINT, sessionId) + (FB_INTL_VARCHAR(255 * 4, CS_UTF8), description) + (FB_TIMESTAMP_TZ, startTimestamp) + (FB_TIMESTAMP_TZ, finishTimestamp) + ) sessionMessage(status, MasterInterfacePtr()); + sessionMessage.clear(); + + constexpr auto statementSql = R"""( + update or insert into fbprof$statements + (session_id, statement_id, parent_statement_id, statement_type, package_name, routine_name, sql_text) + values (?, ?, ?, ?, ?, ?, ?) + matching (session_id, statement_id) + )"""; + + FB_MESSAGE(StatementMessage, ThrowStatusExceptionWrapper, + (FB_BIGINT, sessionId) + (FB_BIGINT, statementId) + (FB_BIGINT, parentStatementId) + (FB_INTL_VARCHAR(20 * 4, CS_UTF8), statementType) + (FB_INTL_VARCHAR(METADATA_IDENTIFIER_CHAR_LEN * 4, CS_UTF8), packageName) + (FB_INTL_VARCHAR(METADATA_IDENTIFIER_CHAR_LEN * 4, CS_UTF8), routineName) + (FB_BLOB, sqlText) + ) statementMessage(status, MasterInterfacePtr()); + statementMessage.clear(); + + constexpr auto recSrcsSql = R"""( + update or insert into fbprof$record_sources + (session_id, statement_id, cursor_id, record_source_id, + parent_record_source_id, access_path) + values (?, ?, ?, ?, ?, ?) + matching (session_id, statement_id, cursor_id, record_source_id) + )"""; + + FB_MESSAGE(RecSrcsMessage, ThrowStatusExceptionWrapper, + (FB_BIGINT, sessionId) + (FB_BIGINT, statementId) + (FB_INTEGER, cursorId) + (FB_INTEGER, recordSourceId) + (FB_BIGINT, parentRecordSourceId) + (FB_INTL_VARCHAR(1024 * 4, CS_UTF8), accessPath) + ) recSrcsMessage(status, MasterInterfacePtr()); + recSrcsMessage.clear(); + + constexpr auto requestSql = R"""( + update or insert into fbprof$requests + (session_id, request_id, statement_id, caller_request_id, start_timestamp, finish_timestamp) + values (?, ?, ?, ?, ?, ?) + matching (session_id, request_id) + )"""; + + FB_MESSAGE(RequestMessage, ThrowStatusExceptionWrapper, + (FB_BIGINT, sessionId) + (FB_BIGINT, requestId) + (FB_BIGINT, statementId) + (FB_BIGINT, callerRequestId) + (FB_TIMESTAMP_TZ, startTimestamp) + (FB_TIMESTAMP_TZ, finishTimestamp) + ) requestMessage(status, MasterInterfacePtr()); + requestMessage.clear(); + + constexpr auto recSrcStatsSql = R"""( + execute block ( + session_id type of column fbprof$record_source_stats.session_id = ?, + request_id type of column fbprof$record_source_stats.request_id = ?, + cursor_id type of column fbprof$record_source_stats.cursor_id = ?, + record_source_id type of column fbprof$record_source_stats.record_source_id = ?, + statement_id type of column fbprof$record_source_stats.statement_id = ?, + open_counter type of column fbprof$record_source_stats.open_counter = ?, + open_min_time type of column fbprof$record_source_stats.open_min_time = ?, + open_max_time type of column fbprof$record_source_stats.open_max_time = ?, + open_total_time type of column fbprof$record_source_stats.open_total_time = ?, + fetch_counter type of column fbprof$record_source_stats.fetch_counter = ?, + fetch_min_time type of column fbprof$record_source_stats.fetch_min_time = ?, + fetch_max_time type of column fbprof$record_source_stats.fetch_max_time = ?, + fetch_total_time type of column fbprof$record_source_stats.fetch_total_time = ? + ) + as + begin + merge into fbprof$record_source_stats + using rdb$database on + session_id = :session_id and + request_id = :request_id and + cursor_id = :cursor_id and + record_source_id = :record_source_id + when not matched then + insert (session_id, request_id, cursor_id, record_source_id, statement_id, + open_counter, open_min_time, open_max_time, open_total_time, + fetch_counter, fetch_min_time, fetch_max_time, fetch_total_time) + values (:session_id, :request_id, :cursor_id, :record_source_id, :statement_id, + :open_counter, :open_min_time, :open_max_time, :open_total_time, + :fetch_counter, :fetch_min_time, :fetch_max_time, :fetch_total_time) + when matched then + update set + open_counter = open_counter + :open_counter, + open_min_time = minvalue(open_min_time, :open_min_time), + open_max_time = maxvalue(open_max_time, :open_max_time), + open_total_time = open_total_time + :open_total_time, + fetch_counter = fetch_counter + :fetch_counter, + fetch_min_time = minvalue(fetch_min_time, :fetch_min_time), + fetch_max_time = maxvalue(fetch_max_time, :fetch_max_time), + fetch_total_time = fetch_total_time + :fetch_total_time; + end + )"""; + + FB_MESSAGE(RecSrcStatsMessage, ThrowStatusExceptionWrapper, + (FB_BIGINT, sessionId) + (FB_BIGINT, requestId) + (FB_INTEGER, cursorId) + (FB_INTEGER, recordSourceId) + (FB_BIGINT, statementId) + (FB_BIGINT, openCounter) + (FB_BIGINT, openMinTime) + (FB_BIGINT, openMaxTime) + (FB_BIGINT, openTotalTime) + (FB_BIGINT, fetchCounter) + (FB_BIGINT, fetchMinTime) + (FB_BIGINT, fetchMaxTime) + (FB_BIGINT, fetchTotalTime) + ) recSrcStatsMessage(status, MasterInterfacePtr()); + recSrcStatsMessage.clear(); + + constexpr auto psqlStatsSql = R"""( + execute block ( + session_id type of column fbprof$psql_stats.session_id = ?, + request_id type of column fbprof$psql_stats.request_id = ?, + line_num type of column fbprof$psql_stats.line_num = ?, + column_num type of column fbprof$psql_stats.column_num = ?, + statement_id type of column fbprof$psql_stats.statement_id = ?, + counter type of column fbprof$psql_stats.counter = ?, + min_time type of column fbprof$psql_stats.min_time = ?, + max_time type of column fbprof$psql_stats.max_time = ?, + total_time type of column fbprof$psql_stats.total_time = ? + ) + as + begin + merge into fbprof$psql_stats + using rdb$database on + session_id = :session_id and + request_id = :request_id and + line_num = :line_num and + column_num = :column_num + when not matched then + insert (session_id, request_id, line_num, column_num, + statement_id, counter, min_time, max_time, total_time) + values (:session_id, :request_id, :line_num, :column_num, + :statement_id, :counter, :min_time, :max_time, :total_time) + when matched then + update set + counter = counter + :counter, + min_time = minvalue(min_time, :min_time), + max_time = maxvalue(max_time, :max_time), + total_time = total_time + :total_time; + end + )"""; + + FB_MESSAGE(PsqlStatsMessage, ThrowStatusExceptionWrapper, + (FB_BIGINT, sessionId) + (FB_BIGINT, requestId) + (FB_INTEGER, lineNum) + (FB_INTEGER, columnNum) + (FB_BIGINT, statementId) + (FB_BIGINT, counter) + (FB_BIGINT, minTime) + (FB_BIGINT, maxTime) + (FB_BIGINT, totalTime) + ) psqlStatsMessage(status, MasterInterfacePtr()); + psqlStatsMessage.clear(); + + auto sessionStmt = makeNoIncRef(attachment->prepare(status, transaction, 0, sessionSql, SQL_DIALECT_CURRENT, 0)); + auto statementStmt = makeNoIncRef(attachment->prepare( + status, transaction, 0, statementSql, SQL_DIALECT_CURRENT, 0)); + auto recSrcsStmt = makeNoIncRef(attachment->prepare( + status, transaction, 0, recSrcsSql, SQL_DIALECT_CURRENT, 0)); + auto requestBatch = makeNoIncRef(attachment->createBatch(status, transaction, 0, requestSql, SQL_DIALECT_CURRENT, + requestMessage.getMetadata(), 0, nullptr)); + auto recSrcStatsBatch = makeNoIncRef(attachment->createBatch( + status, transaction, 0, recSrcStatsSql, SQL_DIALECT_CURRENT, recSrcStatsMessage.getMetadata(), 0, nullptr)); + auto psqlStatsBatch = makeNoIncRef(attachment->createBatch( + status, transaction, 0, psqlStatsSql, SQL_DIALECT_CURRENT, psqlStatsMessage.getMetadata(), 0, nullptr)); + + unsigned requestBatchSize = 0; + unsigned recSrcStatsBatchSize = 0; + unsigned psqlStatsBatchSize = 0; + + auto executeBatch = [&](IBatch* batch, unsigned& batchSize) + { + if (batchSize) + { + batchSize = 0; + + if (auto batchCs = batch->execute(status, transaction)) + batchCs->dispose(); + } + }; + + auto executeBatches = [&]() + { + executeBatch(requestBatch, requestBatchSize); + executeBatch(recSrcStatsBatch, recSrcStatsBatchSize); + executeBatch(psqlStatsBatch, psqlStatsBatchSize); + }; + + auto addBatch = [&](IBatch* batch, unsigned& batchSize, const auto& message) + { + batch->add(status, 1, message.getData()); + + if (++batchSize == 1000) + executeBatches(); + }; + + for (unsigned sessionIdx = 0; sessionIdx < sessions.getCount(); ) + { + auto& session = sessions[sessionIdx]; + + if (session->dirty) + { + sessionMessage->sessionIdNull = FB_FALSE; + sessionMessage->sessionId = session->getId(); + + sessionMessage->descriptionNull = session->description.isEmpty(); + sessionMessage->description.set(session->description.c_str()); + + sessionMessage->startTimestampNull = FB_FALSE; + sessionMessage->startTimestamp = session->startTimestamp; + + sessionMessage->finishTimestampNull = session->finishTimestamp.isUnknown(); + sessionMessage->finishTimestamp = session->finishTimestamp.value; + + sessionStmt->execute(status, transaction, sessionMessage.getMetadata(), + sessionMessage.getData(), nullptr, nullptr); + + session->dirty = false; + } + + RightPooledMap*>> statementsByLevel; + + for (auto& statementIt : session->statements) + { + auto& profileStatement = statementIt.second; + + if (auto currentStatement = &profileStatement; currentStatement->level == 0) + { + Stack stack; + + while (currentStatement && currentStatement->level == 0) + { + stack.push(currentStatement); + currentStatement = session->statements.get(currentStatement->parentStatementId); + } + + unsigned level = currentStatement ? currentStatement->level : 0; + + while (stack.hasData()) + stack.pop()->level = ++level; + } + + auto levelArray = statementsByLevel.getOrPut(profileStatement.level); + levelArray->add(&statementIt); + } + + for (auto& levelIt : statementsByLevel) + { + for (auto statementIt : levelIt.second) + { + auto profileStatementId = statementIt->first; + auto& profileStatement = statementIt->second; + + statementMessage->sessionIdNull = FB_FALSE; + statementMessage->sessionId = session->getId(); + + statementMessage->statementIdNull = FB_FALSE; + statementMessage->statementId = profileStatementId; + + statementMessage->parentStatementIdNull = profileStatement.parentStatementId == 0 ? FB_TRUE : FB_FALSE; + statementMessage->parentStatementId = profileStatement.parentStatementId; + + statementMessage->statementTypeNull = FB_FALSE; + statementMessage->statementType.set(profileStatement.type.c_str()); + + statementMessage->packageNameNull = profileStatement.packageName.isEmpty(); + statementMessage->packageName.set(profileStatement.packageName.c_str()); + + statementMessage->routineNameNull = profileStatement.routineName.isEmpty(); + statementMessage->routineName.set(profileStatement.routineName.c_str()); + + statementMessage->sqlTextNull = profileStatement.sqlText.isEmpty(); + + if (profileStatement.sqlText.hasData()) + { + auto blob = makeNoIncRef(attachment->createBlob( + status, transaction, &statementMessage->sqlText, 0, nullptr)); + blob->putSegment(status, profileStatement.sqlText.length(), profileStatement.sqlText.c_str()); + blob->close(status); + blob.clear(); + } + + statementStmt->execute(status, transaction, statementMessage.getMetadata(), + statementMessage.getData(), nullptr, nullptr); + } + } + + for (const auto& recSourceIt : session->recordSources) + { + const auto statementId = recSourceIt.first.first.first; + const auto cursorId = recSourceIt.first.first.second; + const auto recSourceId = recSourceIt.first.second; + const auto& recSrc = recSourceIt.second; + + recSrcsMessage->sessionIdNull = FB_FALSE; + recSrcsMessage->sessionId = session->getId(); + + recSrcsMessage->statementIdNull = FB_FALSE; + recSrcsMessage->statementId = statementId; + + recSrcsMessage->cursorIdNull = FB_FALSE; + recSrcsMessage->cursorId = cursorId; + + recSrcsMessage->recordSourceIdNull = FB_FALSE; + recSrcsMessage->recordSourceId = recSourceId; + + recSrcsMessage->parentRecordSourceIdNull = !recSrc.parentId.specified; + recSrcsMessage->parentRecordSourceId = recSrc.parentId.value; + + recSrcsMessage->accessPathNull = FB_FALSE; + recSrcsMessage->accessPath.set(recSrc.accessPath.c_str()); + + recSrcsStmt->execute(status, transaction, recSrcsMessage.getMetadata(), + recSrcsMessage.getData(), nullptr, nullptr); + } + + RightPooledMap*>> requestsByLevel; + Array finishedRequests; + + for (auto& requestIt : session->requests) + { + auto& profileRequest = requestIt.second; + + if (auto currentRequest = &profileRequest; currentRequest->level == 0) + { + Stack stack; + + while (currentRequest && currentRequest->level == 0) + { + stack.push(currentRequest); + currentRequest = session->requests.get(currentRequest->callerRequestId); + } + + unsigned level = currentRequest ? currentRequest->level : 0; + + while (stack.hasData()) + stack.pop()->level = ++level; + } + + auto levelArray = requestsByLevel.getOrPut(profileRequest.level); + levelArray->add(&requestIt); + } + + for (auto& levelIt : requestsByLevel) + { + for (auto requestIt : levelIt.second) + { + auto profileRequestId = requestIt->first; + auto& profileRequest = requestIt->second; + + if (profileRequest.dirty) + { + requestMessage->sessionIdNull = FB_FALSE; + requestMessage->sessionId = session->getId(); + + requestMessage->requestIdNull = FB_FALSE; + requestMessage->requestId = profileRequestId; + + requestMessage->statementIdNull = FB_FALSE; + requestMessage->statementId = profileRequest.statementId; + + requestMessage->callerRequestIdNull = profileRequest.callerRequestId == 0 ? FB_TRUE : FB_FALSE; + requestMessage->callerRequestId = profileRequest.callerRequestId; + + requestMessage->startTimestampNull = FB_FALSE; + requestMessage->startTimestamp = profileRequest.startTimestamp; + + requestMessage->finishTimestampNull = profileRequest.finishTimestamp.isUnknown(); + requestMessage->finishTimestamp = profileRequest.finishTimestamp.value; + + addBatch(requestBatch, requestBatchSize, requestMessage); + + if (profileRequest.finishTimestamp.isAssigned()) + finishedRequests.add(profileRequestId); + + profileRequest.dirty = false; + } + + for (const auto& statsIt : profileRequest.recordSourcesStats) + { + const auto& cursorRecSource = statsIt.first; + const auto& stats = statsIt.second; + + recSrcStatsMessage->sessionIdNull = FB_FALSE; + recSrcStatsMessage->sessionId = session->getId(); + + recSrcStatsMessage->requestIdNull = FB_FALSE; + recSrcStatsMessage->requestId = profileRequestId; + + recSrcStatsMessage->cursorIdNull = FB_FALSE; + recSrcStatsMessage->cursorId = cursorRecSource.first; + + recSrcStatsMessage->recordSourceIdNull = FB_FALSE; + recSrcStatsMessage->recordSourceId = cursorRecSource.second; + + recSrcStatsMessage->statementIdNull = FB_FALSE; + recSrcStatsMessage->statementId = profileRequest.statementId; + + recSrcStatsMessage->openCounterNull = FB_FALSE; + recSrcStatsMessage->openCounter = stats.openStats.counter; + + recSrcStatsMessage->openMinTimeNull = FB_FALSE; + recSrcStatsMessage->openMinTime = stats.openStats.minTime; + + recSrcStatsMessage->openMaxTimeNull = FB_FALSE; + recSrcStatsMessage->openMaxTime = stats.openStats.maxTime; + + recSrcStatsMessage->openTotalTimeNull = FB_FALSE; + recSrcStatsMessage->openTotalTime = stats.openStats.totalTime; + + recSrcStatsMessage->fetchCounterNull = FB_FALSE; + recSrcStatsMessage->fetchCounter = stats.fetchStats.counter; + + recSrcStatsMessage->fetchMinTimeNull = FB_FALSE; + recSrcStatsMessage->fetchMinTime = stats.fetchStats.minTime; + + recSrcStatsMessage->fetchMaxTimeNull = FB_FALSE; + recSrcStatsMessage->fetchMaxTime = stats.fetchStats.maxTime; + + recSrcStatsMessage->fetchTotalTimeNull = FB_FALSE; + recSrcStatsMessage->fetchTotalTime = stats.fetchStats.totalTime; + + addBatch(recSrcStatsBatch, recSrcStatsBatchSize, recSrcStatsMessage); + } + + profileRequest.recordSourcesStats.clear(); + + for (const auto& statsIt : profileRequest.psqlStats) + { + const auto& lineColumn = statsIt.first; + + psqlStatsMessage->sessionIdNull = FB_FALSE; + psqlStatsMessage->sessionId = session->getId(); + + psqlStatsMessage->requestIdNull = FB_FALSE; + psqlStatsMessage->requestId = profileRequestId; + + psqlStatsMessage->lineNumNull = FB_FALSE; + psqlStatsMessage->lineNum = lineColumn.first; + + psqlStatsMessage->columnNumNull = FB_FALSE; + psqlStatsMessage->columnNum = lineColumn.second; + + psqlStatsMessage->statementIdNull = FB_FALSE; + psqlStatsMessage->statementId = profileRequest.statementId; + + psqlStatsMessage->counterNull = FB_FALSE; + psqlStatsMessage->counter = statsIt.second.counter; + + psqlStatsMessage->minTimeNull = FB_FALSE; + psqlStatsMessage->minTime = statsIt.second.minTime; + + psqlStatsMessage->maxTimeNull = FB_FALSE; + psqlStatsMessage->maxTime = statsIt.second.maxTime; + + psqlStatsMessage->totalTimeNull = FB_FALSE; + psqlStatsMessage->totalTime = statsIt.second.totalTime; + + addBatch(psqlStatsBatch, psqlStatsBatchSize, psqlStatsMessage); + } + + profileRequest.psqlStats.clear(); + } + } + + if (session->finishTimestamp.isUnknown()) + { + session->statements.clear(); + session->recordSources.clear(); + + for (const auto requestId : finishedRequests) + session->requests.remove(requestId); + + ++sessionIdx; + } + else + sessions.remove(sessionIdx); + } + + executeBatches(); +} + +void ProfilerPlugin::createMetadata(ThrowStatusExceptionWrapper* status, RefPtr transaction) +{ + constexpr const char* createSqlStaments[] = { + "create sequence fbprof$session_id", + + "grant usage on sequence fbprof$session_id to public", + + R"""( + create table fbprof$sessions ( + session_id bigint not null + constraint fbprof$sessions_pk + primary key + using index fbprof$sessions_session, + attachment_id bigint not null, + user_name char(63) character set utf8 not null, + description varchar(255) character set utf8, + start_timestamp timestamp with time zone not null, + finish_timestamp timestamp with time zone + ))""", + + "grant select, update, insert on table fbprof$sessions to public", + + R"""( + create table fbprof$statements ( + session_id bigint not null + constraint fbprof$statements_session_fk + references fbprof$sessions + on delete cascade + using index fbprof$statements_session, + statement_id bigint not null, + parent_statement_id bigint, + statement_type varchar(20) character set utf8 not null, + package_name char(63) character set utf8, + routine_name char(63) character set utf8, + sql_text blob sub_type text character set utf8, + constraint fbprof$statements_pk + primary key (session_id, statement_id) + using index fbprof$statements_session_statement, + constraint fbprof$statements_parent_statement_fk + foreign key (session_id, parent_statement_id) references fbprof$statements (session_id, statement_id) + on delete cascade + using index fbprof$statements_parent_statement + ))""", + + "grant select, update, insert on table fbprof$statements to public", + + R"""( + create table fbprof$record_sources ( + session_id bigint not null + constraint fbprof$record_sources_session_fk + references fbprof$sessions + on delete cascade + using index fbprof$record_sources_session, + statement_id bigint not null, + cursor_id bigint not null, + record_source_id bigint not null, + parent_record_source_id bigint, + access_path varchar(1024) character set utf8 not null, + constraint fbprof$record_sources_pk + primary key (session_id, statement_id, cursor_id, record_source_id) + using index fbprof$record_sources_session_statement_cursor_recsource, + constraint fbprof$record_sources_statement_fk + foreign key (session_id, statement_id) references fbprof$statements + on delete cascade + using index fbprof$record_sources_session_statement, + constraint fbprof$record_sources_parent_record_source_fk + foreign key (session_id, statement_id, cursor_id, parent_record_source_id) + references fbprof$record_sources (session_id, statement_id, cursor_id, record_source_id) + on delete cascade + using index fbprof$record_sources_session_statement_cursor_parent_rec_src + ))""", + + "grant select, update, insert on table fbprof$record_sources to public", + + R"""( + create table fbprof$requests ( + session_id bigint not null + constraint fbprof$requests_session_fk + references fbprof$sessions + on delete cascade + using index fbprof$requests_session, + request_id bigint not null, + statement_id bigint not null, + caller_request_id bigint, + start_timestamp timestamp with time zone not null, + finish_timestamp timestamp with time zone, + constraint fbprof$requests_pk + primary key (session_id, request_id) + using index fbprof$requests_session_request, + constraint fbprof$requests_statement_fk + foreign key (session_id, statement_id) references fbprof$statements + on delete cascade + using index fbprof$requests_session_statement, + constraint fbprof$requests_caller_request_fk + foreign key (session_id, caller_request_id) references fbprof$requests (session_id, request_id) + on delete cascade + using index fbprof$requests_caller_request + ))""", + + "grant select, update, insert on table fbprof$requests to public", + + R"""( + create table fbprof$psql_stats ( + session_id bigint not null + constraint fbprof$psql_stats_session_fk + references fbprof$sessions + on delete cascade + using index fbprof$psql_stats_session, + request_id bigint not null, + line_num integer not null, + column_num integer not null, + statement_id bigint not null, + counter bigint not null, + min_time bigint not null, + max_time bigint not null, + total_time bigint not null, + constraint fbprof$psql_stats_pk + primary key (session_id, request_id, line_num, column_num) + using index fbprof$psql_stats_session_request_line_column, + constraint fbprof$psql_stats_request_fk + foreign key (session_id, request_id) references fbprof$requests + on delete cascade + using index fbprof$psql_stats_session_request, + constraint fbprof$psql_stats_statement_fk + foreign key (session_id, statement_id) references fbprof$statements + on delete cascade + using index fbprof$psql_stats_session_statement + ))""", + + "grant select, update, insert on table fbprof$psql_stats to public", + + R"""( + create table fbprof$record_source_stats ( + session_id bigint not null + constraint fbprof$record_source_stats_session_fk + references fbprof$sessions + on delete cascade + using index fbprof$record_source_stats_session_id, + request_id bigint not null, + cursor_id bigint not null, + record_source_id bigint not null, + statement_id bigint not null, + open_counter bigint not null, + open_min_time bigint not null, + open_max_time bigint not null, + open_total_time bigint not null, + fetch_counter bigint not null, + fetch_min_time bigint not null, + fetch_max_time bigint not null, + fetch_total_time bigint not null, + constraint fbprof$record_source_stats_pk + primary key (session_id, request_id, cursor_id, record_source_id) + using index fbprof$record_source_stats_session_request_cursor_recsource, + constraint fbprof$record_source_stats_request_fk + foreign key (session_id, request_id) references fbprof$requests + on delete cascade + using index fbprof$record_source_stats_session_request, + constraint fbprof$record_source_stats_statement_fk + foreign key (session_id, statement_id) references fbprof$statements + on delete cascade + using index fbprof$record_source_stats_session_statement, + constraint fbprof$record_source_stats_record_source_fk + foreign key (session_id, statement_id, cursor_id, record_source_id) references fbprof$record_sources + on delete cascade + using index fbprof$record_source_stats_statement_cursor_record_source + ))""", + + "grant select, update, insert on table fbprof$record_source_stats to public", + + R"""( + create view fbprof$psql_stats_view + as + select pstat.session_id, + pstat.statement_id, + sta.statement_type, + sta.package_name, + sta.routine_name, + sta.parent_statement_id, + sta_parent.statement_type parent_statement_type, + sta_parent.routine_name parent_routine_name, + (select sql_text + from fbprof$statements + where session_id = pstat.session_id and + statement_id = coalesce(sta.parent_statement_id, pstat.statement_id) + ) sql_text, + pstat.line_num, + pstat.column_num, + sum(pstat.counter) counter, + min(pstat.min_time) min_time, + max(pstat.max_time) max_time, + sum(pstat.total_time) total_time, + sum(pstat.total_time) / nullif(sum(pstat.counter), 0) avg_time + from fbprof$psql_stats pstat + join fbprof$statements sta + on sta.session_id = pstat.session_id and + sta.statement_id = pstat.statement_id + left join fbprof$statements sta_parent + on sta_parent.session_id = sta.session_id and + sta_parent.statement_id = sta.parent_statement_id + group by pstat.session_id, + pstat.statement_id, + sta.statement_type, + sta.package_name, + sta.routine_name, + sta.parent_statement_id, + sta_parent.statement_type, + sta_parent.routine_name, + pstat.line_num, + pstat.column_num + order by sum(pstat.total_time) desc + )""", + + "grant select on table fbprof$psql_stats_view to public", + + R"""( + create view fbprof$record_source_stats_view + as + select rstat.session_id, + rstat.statement_id, + sta.statement_type, + sta.package_name, + sta.routine_name, + sta.parent_statement_id, + sta_parent.statement_type parent_statement_type, + sta_parent.routine_name parent_routine_name, + (select sql_text + from fbprof$statements + where session_id = rstat.session_id and + statement_id = coalesce(sta.parent_statement_id, rstat.statement_id) + ) sql_text, + rstat.cursor_id, + rstat.record_source_id, + recsrc.parent_record_source_id, + recsrc.access_path, + sum(rstat.open_counter) open_counter, + min(rstat.open_min_time) open_min_time, + max(rstat.open_max_time) open_max_time, + sum(rstat.open_total_time) open_total_time, + sum(rstat.open_total_time) / nullif(sum(rstat.open_counter), 0) open_avg_time, + sum(rstat.fetch_counter) fetch_counter, + min(rstat.fetch_min_time) fetch_min_time, + max(rstat.fetch_max_time) fetch_max_time, + sum(rstat.fetch_total_time) fetch_total_time, + sum(rstat.fetch_total_time) / nullif(sum(rstat.fetch_counter), 0) fetch_avg_time, + coalesce(sum(rstat.open_total_time), 0) + coalesce(sum(rstat.fetch_total_time), 0) open_fetch_total_time + from fbprof$record_source_stats rstat + join fbprof$record_sources recsrc + on recsrc.session_id = rstat.session_id and + recsrc.statement_id = rstat.statement_id and + recsrc.cursor_id = rstat.cursor_id and + recsrc.record_source_id = rstat.record_source_id + join fbprof$statements sta + on sta.session_id = rstat.session_id and + sta.statement_id = rstat.statement_id + left join fbprof$statements sta_parent + on sta_parent.session_id = sta.session_id and + sta_parent.statement_id = sta.parent_statement_id + group by rstat.session_id, + rstat.statement_id, + sta.statement_type, + sta.package_name, + sta.routine_name, + sta.parent_statement_id, + sta_parent.statement_type, + sta_parent.routine_name, + rstat.cursor_id, + rstat.record_source_id, + recsrc.parent_record_source_id, + recsrc.access_path + order by coalesce(sum(rstat.open_total_time), 0) + coalesce(sum(rstat.fetch_total_time), 0) desc + )""", + + "grant select on table fbprof$record_source_stats_view to public" + }; + + for (auto createSql : createSqlStaments) + { + attachment->execute(status, transaction, 0, createSql, SQL_DIALECT_CURRENT, + nullptr, nullptr, nullptr, nullptr); + } + + transaction->commit(status); + transaction.clear(); + + transaction = makeNoIncRef(attachment->startTransaction(status, 0, nullptr)); + loadMetadata(status, std::move(transaction)); +} + +// Load objects in engine caches so they can be used in the user's transaction. +void ProfilerPlugin::loadMetadata(ThrowStatusExceptionWrapper* status, RefPtr transaction) +{ + constexpr auto loadObjectsSql = + R"""( + select * + from fbprof$sessions + cross join fbprof$statements + cross join fbprof$record_sources + cross join fbprof$requests + cross join fbprof$psql_stats + cross join fbprof$record_source_stats + where next value for fbprof$session_id = 0 + )"""; + + makeNoIncRef(attachment->prepare(status, transaction, 0, loadObjectsSql, SQL_DIALECT_CURRENT, 0)); + + transaction->commit(status); + transaction.clear(); +} + +//-------------------------------------- + +Session::Session(ThrowStatusExceptionWrapper* status, ProfilerPlugin* aPlugin, ITransaction* transaction, + const char* aDescription, ISC_TIMESTAMP_TZ aStartTimestamp) + : plugin(aPlugin), + startTimestamp(aStartTimestamp), + description(defaultPool(), aDescription) +{ + FB_MESSAGE(SequenceMessage, ThrowStatusExceptionWrapper, + (FB_BIGINT, value) + ) sequenceMessage(status, MasterInterfacePtr()); + sequenceMessage.clear(); + + constexpr auto sequenceSql = "select next value for fbprof$session_id from rdb$database"; + + auto resultSet = makeNoIncRef(plugin->attachment->openCursor(status, transaction, 0, sequenceSql, + SQL_DIALECT_CURRENT, + nullptr, nullptr, sequenceMessage.getMetadata(), nullptr, 0)); + + resultSet->fetchNext(status, sequenceMessage.getData()); + id = sequenceMessage->value; + + plugin->sessions.add(makeRef(this)); + + addRef(); +} + +void Session::finish(ThrowStatusExceptionWrapper* status, ISC_TIMESTAMP_TZ timestamp) +{ + dirty = true; + finishTimestamp = timestamp; +} + +void Session::defineStatement(ThrowStatusExceptionWrapper* status, SINT64 statementId, SINT64 parentStatementId, + const char* type, const char* packageName, const char* routineName, const char* sqlText) +{ + auto statement = statements.put(statementId); + fb_assert(statement); + + if (!statement) + return; + + statement->type = type; + statement->packageName = packageName; + statement->routineName = routineName; + statement->parentStatementId = parentStatementId; + statement->sqlText = sqlText; +} + +void Session::defineRecordSource(SINT64 statementId, unsigned cursorId, unsigned recSourceId, + const char* accessPath, unsigned parentRecordSourceId) +{ + const auto recSource = recordSources.put({{statementId, cursorId}, recSourceId}); + fb_assert(recSource); + + if (!recSource) + return; + + recSource->accessPath = accessPath; + + constexpr unsigned MAX_ACCESS_PATH_CHAR_LEN = 1024; + + if (unsigned len = recSource->accessPath.length(); len > MAX_ACCESS_PATH_CHAR_LEN) + { + auto str = recSource->accessPath.c_str(); + unsigned charLen = 0; + unsigned pos = 0; + unsigned truncPos = 0; + + while (pos < len && charLen <= MAX_ACCESS_PATH_CHAR_LEN) + { + UChar32 c; + U8_NEXT_UNSAFE(str, pos, c); + ++charLen; + + if (charLen == MAX_ACCESS_PATH_CHAR_LEN - 3) + truncPos = pos; + } + + if (charLen > MAX_ACCESS_PATH_CHAR_LEN) + { + recSource->accessPath.resize(truncPos); + recSource->accessPath += "..."; + } + } + + if (parentRecordSourceId) + recSource->parentId = parentRecordSourceId; +} + +void Session::onRequestStart(ThrowStatusExceptionWrapper* status, SINT64 requestId, SINT64 statementId, + SINT64 callerRequestId, ISC_TIMESTAMP_TZ timestamp) +{ + auto request = requests.put(requestId); + ///fb_assert(!request); + + if (!request) + return; + + request->statementId = statementId; + request->callerRequestId = callerRequestId; + request->startTimestamp = timestamp; +} + +void Session::onRequestFinish(ThrowStatusExceptionWrapper* status, SINT64 requestId, ISC_TIMESTAMP_TZ timestamp) +{ + if (auto request = requests.get(requestId)) + { + request->dirty = true; + request->finishTimestamp = timestamp; + } +} + +void Session::afterPsqlLineColumn(SINT64 requestId, unsigned line, unsigned column, FB_UINT64 runTime) +{ + if (auto request = requests.get(requestId)) + { + const auto profileStats = request->psqlStats.getOrPut({line, column}); + profileStats->hit(runTime); + } +} + +void Session::afterRecordSourceOpen(SINT64 requestId, unsigned cursorId, unsigned recSourceId, FB_UINT64 runTime) +{ + if (auto request = requests.get(requestId)) + { + auto stats = request->recordSourcesStats.getOrPut({cursorId, recSourceId}); + stats->openStats.hit(runTime); + } +} + +void Session::afterRecordSourceGetRecord(SINT64 requestId, unsigned cursorId, unsigned recSourceId, FB_UINT64 runTime) +{ + if (auto request = requests.get(requestId)) + { + auto stats = request->recordSourcesStats.getOrPut({cursorId, recSourceId}); + stats->fetchStats.hit(runTime); + } +} + +//-------------------------------------- + +SimpleFactory factory; + +} // anonymous namespace + +extern "C" void FB_EXPORTED FB_PLUGIN_ENTRY_POINT(IMaster* master) +{ + CachedMasterInterface::set(master); + PluginManagerInterfacePtr()->registerPluginFactory(IPluginManager::TYPE_PROFILER, "Default_Profiler", &factory); + getUnloadDetector()->registerMe(); +}