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 (Default_Profiler) is going to be used.
The RDB$PROFILER package can 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 as open and fetch statistics of implicit and explicit SQL cursors.
To collect profile data, a user must first start a profile session with RDB$PROFILER.START_SESSION. This function returns a profile session ID which is later stored in the profiler snapshot tables to be queried and analyzed by the user. A profiler session may be local (same attachment) or remote (another attachment).
Remote profiling just forwards commands to the remote attachment. So, it's possible that a client profiles multiple attachments simultaneously. It's also possible that a locally or remotely started profile session have commands issued by another attachment.
Remotely issued commands require that the target attachment is in an idle state, i.e. not executing others requests. When the target attachment is not idle, the call blocks waiting for that state.
If the remote attachment is from a different user, the calling user must have the system privilege PROFILE_ANY_ATTACHMENT.
After a session is started, PSQL and SQL statements statistics are collected in memory. A profile session collects data only of statements executed in the same attachment associated with the session. Data is aggregated and stored per requests (i.e. a statement execution). When querying snapshot tables, the user may do extra aggregation per statement, or use the auxiliary views that do that automatically.
A session may be paused to temporarily 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 that case, it has the same semantics of finishing the current session with RDB$PROFILER.FINISH_SESSION(FALSE), so snapshots tables are not updated.
To analyze the collected data, the user must flush the data to the snapshot tables, which can be done by finishing or pausing a session (with FLUSH parameter set to TRUE), or calling RDB$PROFILER.FLUSH. Data is flushed using an autonomous transaction (a transaction started and finished for the specific purpose of profiler data update).
When the profiler is active, there is an overhead that makes everything slower. This overhead varies depending on OS, kernel version and CPU hardware and it's difficult to predict.
But sometimes this overhead may be very high, say, greater than 100%.
If this happens in Linux, you may see what clock source it's using with this command:
cat /sys/devices/system/clocksource/clocksource0/current_clocksource
If result is different than tsc, that may be the cause of this problem.
You can see here how to change clocksource, but you must understand it may have others consequences.
Another possible source of slowdown in Linux is this bug.
Below 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('Profile Session 1', null, null, null, 'DETAILED_REQUESTS') 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('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
set search_path to plg$profiler, public, system;
set transaction read committed;
select * from plg$prof_sessions;
select * from plg$prof_psql_stats_view;
select * from plg$prof_record_source_stats_view;
select preq.*
from plg$prof_requests preq
join plg$prof_sessions pses
on pses.profile_id = preq.profile_id and
pses.description = 'Profile Session 1';
select pstat.*
from plg$prof_psql_stats pstat
join plg$prof_sessions pses
on pses.profile_id = pstat.profile_id and
pses.description = 'Profile Session 1'
order by pstat.profile_id,
pstat.request_id,
pstat.line_num,
pstat.column_num;
select pstat.*
from plg$prof_record_source_stats pstat
join plg$prof_sessions pses
on pses.profile_id = pstat.profile_id and
pses.description = 'Profile Session 2'
order by pstat.profile_id,
pstat.request_id,
pstat.cursor_id,
pstat.record_source_id;
START_SESSIONRDB$PROFILER.START_SESSION starts a new profiler session, makes it the current session (of the given ATTACHMENT_ID) and returns its identifier.
If FLUSH_INTERVAL is different than NULL, auto-flush is setup in the same way as manually calling RDB$PROFILER.SET_FLUSH_INTERVAL.
If PLUGIN_NAME is NULL (the default), it uses the database configuration DefaultProfilerPlugin.
PLUGIN_OPTIONS are plugin specific options and currently could be NULL or the string DETAILED_REQUESTS for Default_Profiler plugin.
When DETAILED_REQUESTS is used, PLG$PROF_REQUESTS will store detailed requests data, i.e., one record per each invocation of a statement. This may generate a lot of records, causing RDB$PROFILER.FLUSH to be slow.
When DETAILED_REQUESTS is not used (the default), PLG$PROF_REQUESTS stores an aggregated record per statement, using REQUEST_ID = 0.
Input parameters:
DESCRIPTION type VARCHAR(255) CHARACTER SET UTF8 default NULLFLUSH_INTERVAL type INTEGER default NULLATTACHMENT_ID type BIGINT default NULL (meaning CURRENT_CONNECTION)PLUGIN_NAME type VARCHAR(255) CHARACTER SET UTF8 default NULLPLUGIN_OPTIONS type VARCHAR(255) CHARACTER SET UTF8 default NULLReturn type: BIGINT NOT NULL.
PAUSE_SESSIONRDB$PROFILER.PAUSE_SESSION pauses the current profiler session (of the given ATTACHMENT_ID), 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 (using the same ATTACHMENT_ID).
Input parameters:
FLUSH type BOOLEAN NOT NULL default FALSEATTACHMENT_ID type BIGINT default NULL (meaning CURRENT_CONNECTION)RESUME_SESSIONRDB$PROFILER.RESUME_SESSION resumes the current profiler session (of the given ATTACHMENT_ID), if it was paused, so the next executed statements statistics are collected again.
Input parameters:
ATTACHMENT_ID type BIGINT default NULL (meaning CURRENT_CONNECTION)FINISH_SESSIONRDB$PROFILER.FINISH_SESSION finishes the current profiler session (of the given ATTACHMENT_ID).
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 (using the same ATTACHMENT_ID).
Input parameters:
FLUSH type BOOLEAN NOT NULL default TRUEATTACHMENT_ID type BIGINT default NULL (meaning CURRENT_CONNECTION)CANCEL_SESSIONRDB$PROFILER.CANCEL_SESSION cancels the current profiler session (of the given ATTACHMENT_ID).
All session data present in the profiler plugin is discarded and will not be flushed.
Data already flushed is not deleted automatically.
Input parameters:
ATTACHMENT_ID type BIGINT default NULL (meaning CURRENT_CONNECTION)DISCARDRDB$PROFILER.DISCARD removes all sessions (of the given ATTACHMENT_ID) from memory, without flushing them.
If there is a active session, it is cancelled.
Input parameters:
ATTACHMENT_ID type BIGINT default NULL (meaning CURRENT_CONNECTION)FLUSHRDB$PROFILER.FLUSH updates the snapshot tables with data from the profile sessions (of the given ATTACHMENT_ID) in memory.
After flushing, the data is stored in tables PLG$PROF_SESSIONS, PLG$PROF_STATEMENTS, PLG$PROF_RECORD_SOURCES, PLG$PROF_REQUESTS, PLG$PROF_PSQL_STATS and PLG$PROF_RECORD_SOURCE_STATS and may be read and analyzed by the user.
Data is updated using an autonomous transaction, so if the procedure is called in a snapshot transaction, data will not be directly readable in the same transaction.
Once flush happens, finished sessions are removed from memory.
Input parameters:
ATTACHMENT_ID type BIGINT default NULL (meaning CURRENT_CONNECTION)SET_FLUSH_INTERVALRDB$PROFILER.SET_FLUSH_INTERVAL turns periodic auto-flush on (when FLUSH_INTERVAL is greater than 0) or off (when FLUSH_INTERVAL is equal to 0).
FLUSH_INTERVAL is interpreted as number of seconds.
Input parameters:
FLUSH_INTERVAL type INTEGER NOT NULLATTACHMENT_ID type BIGINT default NULL (meaning CURRENT_CONNECTION)The profiler schema, snapshot tables, views and sequence are automatically created in the first usage of the profiler.
They are owned by the database owner, with usage/read/write permissions for the RDB$PROFILER role, granted by default to PUBLIC.
When a session is deleted, the related data in other profiler snapshot tables are automatically deleted too through foreign keys with DELETE CASCADE option.
Below is the list of tables that stores profile data.
PLG$PROF_SESSIONSPROFILE_ID type BIGINT - Profile session IDATTACHMENT_ID type BIGINT - Attachment IDUSER_NAME type CHAR(63) CHARACTER SET UTF8 - User nameDESCRIPTION type VARCHAR(255) CHARACTER SET UTF8 - Description passed in RDB$PROFILER.START_SESSIONSTART_TIMESTAMP type TIMESTAMP WITH TIME ZONE - Moment the profile session was startedFINISH_TIMESTAMP type TIMESTAMP WITH TIME ZONE - Moment the profile session was finished (NULL when not finished)PROFILE_IDPLG$PROF_STATEMENTSPROFILE_ID type BIGINT - Profile session IDSTATEMENT_ID type BIGINT - Statement IDPARENT_STATEMENT_ID type BIGINT - Parent statement ID - related to sub routinesSTATEMENT_TYPE type VARCHAR(20) CHARACTER SET UTF8 - BLOCK, FUNCTION, PROCEDURE or TRIGGERSCHEMA_NAME type CHAR(63) CHARACTER SET UTF8 - Schema name of FUNCTION, PROCEDURE or TRIGGERPACKAGE_NAME type CHAR(63) CHARACTER SET UTF8 - Package of FUNCTION or PROCEDUREROUTINE_NAME type CHAR(63) CHARACTER SET UTF8 - Routine name of FUNCTION, PROCEDURE or TRIGGERSQL_TEXT type BLOB subtype TEXT CHARACTER SET UTF8 - SQL text for BLOCKPROFILE_ID, STATEMENT_IDPLG$PROF_CURSORSPROFILE_ID type BIGINT - Profile session IDSTATEMENT_ID type BIGINT - Statement IDCURSOR_ID type INTEGER - Cursor IDNAME type CHAR(63) CHARACTER SET UTF8 - Name of explicit cursorLINE_NUM type INTEGER - Line number of the cursorCOLUMN_NUM type INTEGER - Column number of the cursorPROFILE_ID, STATEMENT_ID, CURSOR_IDPLG$PROF_RECORD_SOURCESPROFILE_ID type BIGINT - Profile session IDSTATEMENT_ID type BIGINT - Statement IDCURSOR_ID type INTEGER - Cursor IDRECORD_SOURCE_ID type INTEGER - Record source IDPARENT_RECORD_SOURCE_ID type INTEGER - Parent record source IDLEVEL type INTEGER - Indentation level for the record sourceACCESS_PATH type VARCHAR(255) CHARACTER SET UTF8 - Access path for the record sourcePROFILE_ID, STATEMENT_ID, CURSOR_ID, RECORD_SOURCE_IDPLG$PROF_REQUESTSPROFILE_ID type BIGINT - Profile session IDSTATEMENT_ID type BIGINT - Statement IDREQUEST_ID type BIGINT - Request IDCALLER_STATEMENT_ID type BIGINT - Caller statement IDCALLER_REQUEST_ID type BIGINT - Caller request IDSTART_TIMESTAMP type TIMESTAMP WITH TIME ZONE - Moment this request was first gathered profile dataFINISH_TIMESTAMP type TIMESTAMP WITH TIME ZONE - Moment this request was finishedTOTAL_ELAPSED_TIME type BIGINT - Accumulated elapsed time (in nanoseconds) of the requestPROFILE_ID, STATEMENT_ID, REQUEST_IDPLG$PROF_PSQL_STATSPROFILE_ID type BIGINT - Profile session IDSTATEMENT_ID type BIGINT - Statement IDREQUEST_ID type BIGINT - Request IDLINE_NUM type INTEGER - Line number of the statementCOLUMN_NUM type INTEGER - Column number of the statementCOUNTER type BIGINT - Number of executed times of the line/columnMIN_ELAPSED_TIME type BIGINT - Minimal elapsed time (in nanoseconds) of a line/column executionMAX_ELAPSED_TIME type BIGINT - Maximum elapsed time (in nanoseconds) of a line/column executionTOTAL_ELAPSED_TIME type BIGINT - Accumulated elapsed time (in nanoseconds) of the line/column executionsPROFILE_ID, STATEMENT_ID, REQUEST_ID, LINE_NUM, COLUMN_NUMPLG$PROF_RECORD_SOURCE_STATSPROFILE_ID type BIGINT - Profile session IDSTATEMENT_ID type BIGINT - Statement IDREQUEST_ID type BIGINT - Request IDCURSOR_ID type INTEGER - Cursor IDRECORD_SOURCE_ID type INTEGER - Record source IDOPEN_COUNTER type BIGINT - Number of open times of the record sourceOPEN_MIN_ELAPSED_TIME type BIGINT - Minimal elapsed time (in nanoseconds) of a record source openOPEN_MAX_ELAPSED_TIME type BIGINT - Maximum elapsed time (in nanoseconds) of a record source openOPEN_TOTAL_ELAPSED_TIME type BIGINT - Accumulated elapsed time (in nanoseconds) of the record source openingsFETCH_COUNTER type BIGINT - Number of fetch times of the record sourceFETCH_MIN_ELAPSED_TIME type BIGINT - Minimal elapsed time (in nanoseconds) of a record source fetchFETCH_MAX_ELAPSED_TIME type BIGINT - Maximum elapsed time (in nanoseconds) of a record source fetchFETCH_TOTAL_ELAPSED_TIME type BIGINT - Accumulated elapsed time (in nanoseconds) of the record source fetchesPROFILE_ID, STATEMENT_ID, REQUEST_ID, CURSOR_ID, RECORD_SOURCE_IDThese 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 hotspots are found, one can drill down in the data at the request level through the tables.
PLG$PROF_STATEMENT_STATS_VIEWselect req.profile_id,
req.statement_id,
sta.statement_type,
sta.schema_name,
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 plg$prof_statements
where profile_id = req.profile_id and
statement_id = coalesce(sta.parent_statement_id, req.statement_id)
) sql_text,
count(*) counter,
min(req.total_elapsed_time) min_elapsed_time,
max(req.total_elapsed_time) max_elapsed_time,
cast(sum(req.total_elapsed_time) as bigint) total_elapsed_time,
cast(sum(req.total_elapsed_time) / count(*) as bigint) avg_elapsed_time
from plg$prof_requests req
join plg$prof_statements sta
on sta.profile_id = req.profile_id and
sta.statement_id = req.statement_id
left join plg$prof_statements sta_parent
on sta_parent.profile_id = sta.profile_id and
sta_parent.statement_id = sta.parent_statement_id
group by req.profile_id,
req.statement_id,
sta.statement_type,
sta.schema_name,
sta.package_name,
sta.routine_name,
sta.parent_statement_id,
sta_parent.statement_type,
sta_parent.routine_name
order by sum(req.total_elapsed_time) desc
PLG$PROF_PSQL_STATS_VIEWselect pstat.profile_id,
pstat.statement_id,
sta.statement_type,
sta.schema_name,
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 plg$prof_statements
where profile_id = pstat.profile_id and
statement_id = coalesce(sta.parent_statement_id, pstat.statement_id)
) sql_text,
pstat.line_num,
pstat.column_num,
cast(sum(pstat.counter) as bigint) counter,
min(pstat.min_elapsed_time) min_elapsed_time,
max(pstat.max_elapsed_time) max_elapsed_time,
cast(sum(pstat.total_elapsed_time) as bigint) total_elapsed_time,
cast(sum(pstat.total_elapsed_time) / nullif(sum(pstat.counter), 0) as bigint) avg_elapsed_time
from plg$prof_psql_stats pstat
join plg$prof_statements sta
on sta.profile_id = pstat.profile_id and
sta.statement_id = pstat.statement_id
left join plg$prof_statements sta_parent
on sta_parent.profile_id = sta.profile_id and
sta_parent.statement_id = sta.parent_statement_id
group by pstat.profile_id,
pstat.statement_id,
sta.statement_type,
sta.schema_name,
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_elapsed_time) desc
PLG$PROF_RECORD_SOURCE_STATS_VIEWselect rstat.profile_id,
rstat.statement_id,
sta.statement_type,
sta.schema_name,
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 plg$prof_statements
where profile_id = rstat.profile_id and
statement_id = coalesce(sta.parent_statement_id, rstat.statement_id)
) sql_text,
rstat.cursor_id,
cur.name cursor_name,
cur.line_num cursor_line_num,
cur.column_num cursor_column_num,
rstat.record_source_id,
recsrc.parent_record_source_id,
recsrc.level,
recsrc.access_path,
cast(sum(rstat.open_counter) as bigint) open_counter,
min(rstat.open_min_elapsed_time) open_min_elapsed_time,
max(rstat.open_max_elapsed_time) open_max_elapsed_time,
cast(sum(rstat.open_total_elapsed_time) as bigint) open_total_elapsed_time,
cast(sum(rstat.open_total_elapsed_time) / nullif(sum(rstat.open_counter), 0) as bigint) open_avg_elapsed_time,
cast(sum(rstat.fetch_counter) as bigint) fetch_counter,
min(rstat.fetch_min_elapsed_time) fetch_min_elapsed_time,
max(rstat.fetch_max_elapsed_time) fetch_max_elapsed_time,
cast(sum(rstat.fetch_total_elapsed_time) as bigint) fetch_total_elapsed_time,
cast(sum(rstat.fetch_total_elapsed_time) / nullif(sum(rstat.fetch_counter), 0) as bigint) fetch_avg_elapsed_time,
cast(coalesce(sum(rstat.open_total_elapsed_time), 0) + coalesce(sum(rstat.fetch_total_elapsed_time), 0) as bigint) open_fetch_total_elapsed_time
from plg$prof_record_source_stats rstat
join plg$prof_cursors cur
on cur.profile_id = rstat.profile_id and
cur.statement_id = rstat.statement_id and
cur.cursor_id = rstat.cursor_id
join plg$prof_record_sources recsrc
on recsrc.profile_id = rstat.profile_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 plg$prof_statements sta
on sta.profile_id = rstat.profile_id and
sta.statement_id = rstat.statement_id
left join plg$prof_statements sta_parent
on sta_parent.profile_id = sta.profile_id and
sta_parent.statement_id = sta.parent_statement_id
group by rstat.profile_id,
rstat.statement_id,
sta.statement_type,
sta.schema_name,
sta.package_name,
sta.routine_name,
sta.parent_statement_id,
sta_parent.statement_type,
sta_parent.routine_name,
rstat.cursor_id,
cur.name,
cur.line_num,
cur.column_num,
rstat.record_source_id,
recsrc.parent_record_source_id,
recsrc.level,
recsrc.access_path
order by coalesce(sum(rstat.open_total_elapsed_time), 0) + coalesce(sum(rstat.fetch_total_elapsed_time), 0) desc