From 4595a2c0e3e348279b76d8e7c30e67ad39318733 Mon Sep 17 00:00:00 2001 From: Adriano dos Santos Fernandes Date: Tue, 10 Jan 2023 20:45:24 -0300 Subject: [PATCH] Add PLG$PROF_CURSORS and some others fixes. --- doc/sql.extensions/README.profiler.md | 20 ++++ src/include/firebird/FirebirdInterface.idl | 2 +- src/include/firebird/IdlFbInterfaces.h | 20 ++++ src/include/gen/Firebird.pas | 19 ++++ src/jrd/ProfilerManager.cpp | 20 ++++ src/jrd/ProfilerManager.h | 4 + src/jrd/recsrc/Cursor.cpp | 22 ++++ src/jrd/recsrc/Cursor.h | 24 +++- src/plugins/profiler/Profiler.cpp | 125 ++++++++++++++++++++- 9 files changed, 245 insertions(+), 11 deletions(-) diff --git a/doc/sql.extensions/README.profiler.md b/doc/sql.extensions/README.profiler.md index 85953527a0..35aa8b8a8a 100644 --- a/doc/sql.extensions/README.profiler.md +++ b/doc/sql.extensions/README.profiler.md @@ -248,6 +248,16 @@ Below is the list of tables that stores profile data. - `SQL_TEXT` type `BLOB subtype TEXT CHARACTER SET UTF8` - SQL text for BLOCK - Primary key: `PROFILE_ID, STATEMENT_ID` +## Table `PLG$PROF_CURSORS` + + - `PROFILE_ID` type `BIGINT` - Profile session ID + - `STATEMENT_ID` type `BIGINT` - Statement ID + - `CURSOR_ID` type `INTEGER` - Cursor ID + - `NAME` type `CHAR(63) CHARACTER SET UTF8` - Name of explicit cursor + - `LINE_NUM` type `INTEGER` - Line number of the cursor + - `COLUMN_NUM` type `INTEGER` - Column number of the cursor + - Primary key: `PROFILE_ID, STATEMENT_ID, CURSOR_ID` + ## Table `PLG$PROF_RECORD_SOURCES` - `PROFILE_ID` type `BIGINT` - Profile session ID @@ -403,6 +413,9 @@ select rstat.profile_id, 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.access_path, @@ -418,6 +431,10 @@ select rstat.profile_id, 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 @@ -438,6 +455,9 @@ select rstat.profile_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.access_path diff --git a/src/include/firebird/FirebirdInterface.idl b/src/include/firebird/FirebirdInterface.idl index 617f1bc53a..6035ac27ca 100644 --- a/src/include/firebird/FirebirdInterface.idl +++ b/src/include/firebird/FirebirdInterface.idl @@ -1741,7 +1741,7 @@ interface ProfilerSession : Disposable 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 defineCursor(int64 statementId, uint cursorId, const string name, uint line, uint column); void defineRecordSource(int64 statementId, uint cursorId, uint recSourceId, const string accessPath, uint parentRecSourceId); diff --git a/src/include/firebird/IdlFbInterfaces.h b/src/include/firebird/IdlFbInterfaces.h index 44f4dbf09c..be925cb45e 100644 --- a/src/include/firebird/IdlFbInterfaces.h +++ b/src/include/firebird/IdlFbInterfaces.h @@ -6953,6 +6953,7 @@ namespace Firebird void (CLOOP_CARG *cancel)(IProfilerSession* self, IStatus* status) CLOOP_NOEXCEPT; void (CLOOP_CARG *finish)(IProfilerSession* self, IStatus* status, ISC_TIMESTAMP_TZ timestamp) CLOOP_NOEXCEPT; 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) CLOOP_NOEXCEPT; + void (CLOOP_CARG *defineCursor)(IProfilerSession* self, ISC_INT64 statementId, unsigned cursorId, const char* name, unsigned line, unsigned column) CLOOP_NOEXCEPT; void (CLOOP_CARG *defineRecordSource)(IProfilerSession* self, ISC_INT64 statementId, unsigned cursorId, unsigned recSourceId, const char* accessPath, unsigned parentRecSourceId) CLOOP_NOEXCEPT; void (CLOOP_CARG *onRequestStart)(IProfilerSession* self, IStatus* status, ISC_INT64 requestId, ISC_INT64 statementId, ISC_INT64 callerRequestId, ISC_TIMESTAMP_TZ timestamp) CLOOP_NOEXCEPT; void (CLOOP_CARG *onRequestFinish)(IProfilerSession* self, IStatus* status, ISC_INT64 requestId, ISC_TIMESTAMP_TZ timestamp, IProfilerStats* stats) CLOOP_NOEXCEPT; @@ -7013,6 +7014,11 @@ namespace Firebird StatusType::checkException(status); } + void defineCursor(ISC_INT64 statementId, unsigned cursorId, const char* name, unsigned line, unsigned column) + { + static_cast(this->cloopVTable)->defineCursor(this, statementId, cursorId, name, line, column); + } + 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); @@ -20426,6 +20432,7 @@ namespace Firebird this->cancel = &Name::cloopcancelDispatcher; this->finish = &Name::cloopfinishDispatcher; this->defineStatement = &Name::cloopdefineStatementDispatcher; + this->defineCursor = &Name::cloopdefineCursorDispatcher; this->defineRecordSource = &Name::cloopdefineRecordSourceDispatcher; this->onRequestStart = &Name::clooponRequestStartDispatcher; this->onRequestFinish = &Name::clooponRequestFinishDispatcher; @@ -20509,6 +20516,18 @@ namespace Firebird } } + static void CLOOP_CARG cloopdefineCursorDispatcher(IProfilerSession* self, ISC_INT64 statementId, unsigned cursorId, const char* name, unsigned line, unsigned column) CLOOP_NOEXCEPT + { + try + { + static_cast(self)->Name::defineCursor(statementId, cursorId, name, line, column); + } + catch (...) + { + StatusType::catchException(0); + } + } + static void CLOOP_CARG cloopdefineRecordSourceDispatcher(IProfilerSession* self, ISC_INT64 statementId, unsigned cursorId, unsigned recSourceId, const char* accessPath, unsigned parentRecSourceId) CLOOP_NOEXCEPT { try @@ -20652,6 +20671,7 @@ namespace Firebird virtual void cancel(StatusType* status) = 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 defineCursor(ISC_INT64 statementId, unsigned cursorId, const char* name, unsigned line, unsigned column) = 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, IProfilerStats* stats) = 0; diff --git a/src/include/gen/Firebird.pas b/src/include/gen/Firebird.pas index 6e3911c7ed..2b85a1feb0 100644 --- a/src/include/gen/Firebird.pas +++ b/src/include/gen/Firebird.pas @@ -726,6 +726,7 @@ type IProfilerSession_cancelPtr = procedure(this: IProfilerSession; status: IStatus); 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_defineCursorPtr = procedure(this: IProfilerSession; statementId: Int64; cursorId: Cardinal; name: PAnsiChar; line: Cardinal; column: Cardinal); 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; stats: IProfilerStats); cdecl; @@ -3814,6 +3815,7 @@ type cancel: IProfilerSession_cancelPtr; finish: IProfilerSession_finishPtr; defineStatement: IProfilerSession_defineStatementPtr; + defineCursor: IProfilerSession_defineCursorPtr; defineRecordSource: IProfilerSession_defineRecordSourcePtr; onRequestStart: IProfilerSession_onRequestStartPtr; onRequestFinish: IProfilerSession_onRequestFinishPtr; @@ -3835,6 +3837,7 @@ type procedure cancel(status: IStatus); 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 defineCursor(statementId: Int64; cursorId: Cardinal; name: PAnsiChar; line: Cardinal; column: Cardinal); 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; stats: IProfilerStats); @@ -3855,6 +3858,7 @@ type procedure cancel(status: IStatus); 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 defineCursor(statementId: Int64; cursorId: Cardinal; name: PAnsiChar; line: Cardinal; column: Cardinal); 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; stats: IProfilerStats); virtual; abstract; @@ -9082,6 +9086,11 @@ begin FbException.checkException(status); end; +procedure IProfilerSession.defineCursor(statementId: Int64; cursorId: Cardinal; name: PAnsiChar; line: Cardinal; column: Cardinal); +begin + ProfilerSessionVTable(vTable).defineCursor(Self, statementId, cursorId, name, line, column); +end; + procedure IProfilerSession.defineRecordSource(statementId: Int64; cursorId: Cardinal; recSourceId: Cardinal; accessPath: PAnsiChar; parentRecSourceId: Cardinal); begin ProfilerSessionVTable(vTable).defineRecordSource(Self, statementId, cursorId, recSourceId, accessPath, parentRecSourceId); @@ -15788,6 +15797,15 @@ begin end end; +procedure IProfilerSessionImpl_defineCursorDispatcher(this: IProfilerSession; statementId: Int64; cursorId: Cardinal; name: PAnsiChar; line: Cardinal; column: Cardinal); cdecl; +begin + try + IProfilerSessionImpl(this).defineCursor(statementId, cursorId, name, line, column); + except + on e: Exception do FbException.catchException(nil, e); + end +end; + procedure IProfilerSessionImpl_defineRecordSourceDispatcher(this: IProfilerSession; statementId: Int64; cursorId: Cardinal; recSourceId: Cardinal; accessPath: PAnsiChar; parentRecSourceId: Cardinal); cdecl; begin try @@ -16903,6 +16921,7 @@ initialization IProfilerSessionImpl_vTable.cancel := @IProfilerSessionImpl_cancelDispatcher; IProfilerSessionImpl_vTable.finish := @IProfilerSessionImpl_finishDispatcher; IProfilerSessionImpl_vTable.defineStatement := @IProfilerSessionImpl_defineStatementDispatcher; + IProfilerSessionImpl_vTable.defineCursor := @IProfilerSessionImpl_defineCursorDispatcher; IProfilerSessionImpl_vTable.defineRecordSource := @IProfilerSessionImpl_defineRecordSourceDispatcher; IProfilerSessionImpl_vTable.onRequestStart := @IProfilerSessionImpl_onRequestStartDispatcher; IProfilerSessionImpl_vTable.onRequestFinish := @IProfilerSessionImpl_onRequestFinishDispatcher; diff --git a/src/jrd/ProfilerManager.cpp b/src/jrd/ProfilerManager.cpp index 1d3782fb5e..f100e96e3b 100644 --- a/src/jrd/ProfilerManager.cpp +++ b/src/jrd/ProfilerManager.cpp @@ -464,6 +464,24 @@ SINT64 ProfilerManager::startSession(thread_db* tdbb, Nullable flushInter return currentSession->pluginSession->getId(); } +void ProfilerManager::prepareCursor(thread_db* tdbb, Request* request, const Cursor* cursor) +{ + auto profileStatement = getStatement(request); + + if (!profileStatement) + return; + + auto cursorId = cursor->getCursorProfileId(); + + if (profileStatement->definedCursors.exist(cursorId)) + return; + + currentSession->pluginSession->defineCursor(profileStatement->id, cursorId, + cursor->getName().nullStr(), cursor->getLine(), cursor->getColumn()); + + profileStatement->definedCursors.add(cursorId); +} + void ProfilerManager::prepareRecSource(thread_db* tdbb, Request* request, const RecordSource* rsb) { auto profileStatement = getStatement(request); @@ -474,6 +492,8 @@ void ProfilerManager::prepareRecSource(thread_db* tdbb, Request* request, const if (profileStatement->recSourceSequence.exist(rsb->getRecSourceProfileId())) return; + fb_assert(profileStatement->definedCursors.exist(rsb->getCursorProfileId())); + Array> tree; tree.add({rsb, nullptr}); diff --git a/src/jrd/ProfilerManager.h b/src/jrd/ProfilerManager.h index f90b0f1ea8..994e9790ba 100644 --- a/src/jrd/ProfilerManager.h +++ b/src/jrd/ProfilerManager.h @@ -35,6 +35,7 @@ namespace Jrd { class Attachment; +class Cursor; class Request; class RecordSource; class thread_db; @@ -71,6 +72,7 @@ private: public: Statement(MemoryPool& pool) : cursorNextSequence(pool), + definedCursors(pool), recSourceSequence(pool) { } @@ -80,6 +82,7 @@ private: SINT64 id = 0; Firebird::NonPooledMap cursorNextSequence; + Firebird::SortedArray definedCursors; Firebird::NonPooledMap recSourceSequence; }; @@ -120,6 +123,7 @@ public: SINT64 startSession(thread_db* tdbb, Nullable flushInterval, const Firebird::PathName& pluginName, const Firebird::string& description, const Firebird::string& options); + void prepareCursor(thread_db* tdbb, Request* request, const Cursor* cursor); void prepareRecSource(thread_db* tdbb, Request* request, const RecordSource* rsb); void onRequestFinish(Request* request, Stats& stats); void beforePsqlLineColumn(Request* request, ULONG line, ULONG column); diff --git a/src/jrd/recsrc/Cursor.cpp b/src/jrd/recsrc/Cursor.cpp index 05d702aa87..d50b22ed52 100644 --- a/src/jrd/recsrc/Cursor.cpp +++ b/src/jrd/recsrc/Cursor.cpp @@ -146,6 +146,7 @@ bool SubQuery::fetch(thread_db* tdbb) const Cursor::Cursor(CompilerScratch* csb, const RecordSource* rsb, const RseNode* rse, bool updateCounters, ULONG line, ULONG column, const MetaName& name) : Select(rsb, rse, line, column, name), + m_cursorProfileId(rsb->getCursorProfileId()), m_updateCounters(updateCounters) { fb_assert(m_top); @@ -156,6 +157,9 @@ Cursor::Cursor(CompilerScratch* csb, const RecordSource* rsb, const RseNode* rse void Cursor::open(thread_db* tdbb) const { const auto request = tdbb->getRequest(); + + prepareProfiler(tdbb, request); + Impure* impure = request->getImpure(m_impure); impure->irsb_active = true; @@ -197,6 +201,8 @@ bool Cursor::fetchNext(thread_db* tdbb) const if (impure->irsb_state == EOS) return false; + prepareProfiler(tdbb, request); + if (!m_top->getRecord(tdbb)) { impure->irsb_state = EOS; @@ -287,6 +293,8 @@ bool Cursor::fetchAbsolute(thread_db* tdbb, SINT64 offset) const return false; } + prepareProfiler(tdbb, request); + impure->irsb_position = position; buffer->locate(tdbb, impure->irsb_position); @@ -364,6 +372,8 @@ bool Cursor::fetchRelative(thread_db* tdbb, SINT64 offset) const return false; } + prepareProfiler(tdbb, request); + impure->irsb_position = position; buffer->locate(tdbb, impure->irsb_position); @@ -402,3 +412,15 @@ void Cursor::checkState(Request* request) const Arg::Str(m_cursorName)); } } + +void Cursor::prepareProfiler(thread_db* tdbb, Request* request) const +{ + const auto attachment = tdbb->getAttachment(); + + const auto profilerManager = attachment->isProfilerActive() && !request->hasInternalStatement() ? + attachment->getProfilerManager(tdbb) : + nullptr; + + if (profilerManager) + profilerManager->prepareCursor(tdbb, request, this); +} diff --git a/src/jrd/recsrc/Cursor.h b/src/jrd/recsrc/Cursor.h index f0b6fb59e6..3c76f33957 100644 --- a/src/jrd/recsrc/Cursor.h +++ b/src/jrd/recsrc/Cursor.h @@ -57,6 +57,16 @@ namespace Jrd return m_top; } + ULONG getLine() const + { + return m_line; + } + + ULONG getColumn() const + { + return m_column; + } + void initializeInvariants(Request* request) const; void printPlan(thread_db* tdbb, Firebird::string& plan, bool detailed) const; @@ -115,10 +125,12 @@ namespace Jrd void checkState(Request* request) const; -#if (!defined __GNUC__) || (__GNUC__ > 6) - constexpr -#endif - bool isUpdateCounters() const + ULONG getCursorProfileId() const + { + return m_cursorProfileId; + } + + bool isUpdateCounters() const { return m_updateCounters; } @@ -129,6 +141,10 @@ namespace Jrd } private: + void prepareProfiler(thread_db* tdbb, Request* request) const; + + private: + const ULONG m_cursorProfileId; ULONG m_impure; const bool m_updateCounters; }; diff --git a/src/plugins/profiler/Profiler.cpp b/src/plugins/profiler/Profiler.cpp index 32a66fe259..66f4ff16fb 100644 --- a/src/plugins/profiler/Profiler.cpp +++ b/src/plugins/profiler/Profiler.cpp @@ -86,6 +86,13 @@ struct Stats FB_UINT64 totalElapsedTime = 0; }; +struct Cursor +{ + MetaString name{defaultPool()}; + unsigned line; + unsigned column; +}; + struct RecordSource { Nullable parentId; @@ -124,6 +131,7 @@ struct Request NonPooledMap psqlStats{defaultPool()}; }; +using StatementCursorKey = NonPooledPair; using StatementCursorRecSourceKey = NonPooledPair, unsigned>; class Session final : @@ -159,6 +167,8 @@ public: void defineStatement(ThrowStatusExceptionWrapper* status, SINT64 statementId, SINT64 parentStatementId, const char* type, const char* packageName, const char* routineName, const char* sqlText) override; + void defineCursor(SINT64 statementId, unsigned cursorId, const char* name, unsigned line, unsigned column) override; + void defineRecordSource(SINT64 statementId, unsigned cursorId, unsigned recSourceId, const char* accessPath, unsigned parentRecordSourceId) override; @@ -191,6 +201,7 @@ public: public: RefPtr plugin; NonPooledMap statements{defaultPool()}; + NonPooledMap cursors{defaultPool()}; NonPooledMap recordSources{defaultPool()}; NonPooledMap requests{defaultPool()}; SINT64 id; @@ -374,6 +385,23 @@ void ProfilerPlugin::flush(ThrowStatusExceptionWrapper* status) ) statementMessage(status, MasterInterfacePtr()); statementMessage.clear(); + constexpr auto cursorSql = R"""( + update or insert into plg$prof_cursors + (profile_id, statement_id, cursor_id, name, line_num, column_num) + values (?, ?, ?, ?, ?, ?) + matching (profile_id, statement_id, cursor_id) + )"""; + + FB_MESSAGE(CursorMessage, ThrowStatusExceptionWrapper, + (FB_BIGINT, profileId) + (FB_BIGINT, statementId) + (FB_INTEGER, cursorId) + (FB_INTL_VARCHAR(METADATA_IDENTIFIER_CHAR_LEN * 4, CS_UTF8), name) + (FB_BIGINT, lineNum) + (FB_BIGINT, columnNum) + ) cursorMessage(status, MasterInterfacePtr()); + cursorMessage.clear(); + constexpr auto recSrcSql = R"""( update or insert into plg$prof_record_sources (profile_id, statement_id, cursor_id, record_source_id, @@ -387,7 +415,7 @@ void ProfilerPlugin::flush(ThrowStatusExceptionWrapper* status) (FB_BIGINT, statementId) (FB_INTEGER, cursorId) (FB_INTEGER, recordSourceId) - (FB_BIGINT, parentRecordSourceId) + (FB_INTEGER, parentRecordSourceId) (FB_INTL_VARCHAR(1024 * 4, CS_UTF8), accessPath) ) recSrcMessage(status, MasterInterfacePtr()); recSrcMessage.clear(); @@ -523,11 +551,13 @@ void ProfilerPlugin::flush(ThrowStatusExceptionWrapper* status) auto sessionStmt = makeNoIncRef(userAttachment->prepare(status, transaction, 0, sessionSql, SQL_DIALECT_CURRENT, 0)); auto statementStmt = makeNoIncRef(userAttachment->prepare( status, transaction, 0, statementSql, SQL_DIALECT_CURRENT, 0)); - auto recSrcsStmt = makeNoIncRef(userAttachment->prepare( + auto cursorStmt = makeNoIncRef(userAttachment->prepare( + status, transaction, 0, cursorSql, SQL_DIALECT_CURRENT, 0)); + auto recSrcStmt = makeNoIncRef(userAttachment->prepare( status, transaction, 0, recSrcSql, SQL_DIALECT_CURRENT, 0)); auto requestBatch = makeNoIncRef(userAttachment->createBatch(status, transaction, 0, requestSql, SQL_DIALECT_CURRENT, requestMessage.getMetadata(), 0, nullptr)); - auto recSrcStatsBatch = makeNoIncRef(userAttachment->createBatch( + auto recSrcStatBatch = makeNoIncRef(userAttachment->createBatch( status, transaction, 0, recSrcStatSql, SQL_DIALECT_CURRENT, recSrcStatMessage.getMetadata(), 0, nullptr)); auto psqlStatBatch = makeNoIncRef(userAttachment->createBatch( status, transaction, 0, psqlStatSql, SQL_DIALECT_CURRENT, psqlStatMessage.getMetadata(), 0, nullptr)); @@ -550,7 +580,7 @@ void ProfilerPlugin::flush(ThrowStatusExceptionWrapper* status) auto executeBatches = [&]() { executeBatch(requestBatch, requestBatchSize); - executeBatch(recSrcStatsBatch, recSrcStatBatchSize); + executeBatch(recSrcStatBatch, recSrcStatBatchSize); executeBatch(psqlStatBatch, psqlStatBatchSize); }; @@ -653,6 +683,34 @@ void ProfilerPlugin::flush(ThrowStatusExceptionWrapper* status) } } + for (const auto& cursorIt : session->cursors) + { + const auto statementId = cursorIt.first.first; + const auto cursorId = cursorIt.first.second; + const auto& cursor = cursorIt.second; + + cursorMessage->profileIdNull = FB_FALSE; + cursorMessage->profileId = session->getId(); + + cursorMessage->statementIdNull = FB_FALSE; + cursorMessage->statementId = statementId; + + cursorMessage->cursorIdNull = FB_FALSE; + cursorMessage->cursorId = cursorId; + + cursorMessage->nameNull = cursor.name.isEmpty(); + cursorMessage->name.set(cursor.name.c_str()); + + cursorMessage->lineNumNull = cursor.line == 0 ? FB_TRUE : FB_FALSE; + cursorMessage->lineNum = cursor.line; + + cursorMessage->columnNumNull = cursor.column == 0 ? FB_TRUE : FB_FALSE; + cursorMessage->columnNum = cursor.column; + + cursorStmt->execute(status, transaction, cursorMessage.getMetadata(), + cursorMessage.getData(), nullptr, nullptr); + } + for (const auto& recSourceIt : session->recordSources) { const auto statementId = recSourceIt.first.first.first; @@ -678,7 +736,7 @@ void ProfilerPlugin::flush(ThrowStatusExceptionWrapper* status) recSrcMessage->accessPathNull = FB_FALSE; recSrcMessage->accessPath.set(recSrc.accessPath.c_str()); - recSrcsStmt->execute(status, transaction, recSrcMessage.getMetadata(), + recSrcStmt->execute(status, transaction, recSrcMessage.getMetadata(), recSrcMessage.getData(), nullptr, nullptr); } @@ -791,7 +849,7 @@ void ProfilerPlugin::flush(ThrowStatusExceptionWrapper* status) recSrcStatMessage->fetchTotalElapsedTimeNull = FB_FALSE; recSrcStatMessage->fetchTotalElapsedTime = stats.fetchStats.totalElapsedTime; - addBatch(recSrcStatsBatch, recSrcStatBatchSize, recSrcStatMessage); + addBatch(recSrcStatBatch, recSrcStatBatchSize, recSrcStatMessage); } profileRequest.recordSourcesStats.clear(); @@ -838,6 +896,7 @@ void ProfilerPlugin::flush(ThrowStatusExceptionWrapper* status) { session->statements.clear(); session->recordSources.clear(); + session->cursors.clear(); for (const auto requestId : finishedRequests) session->requests.remove(requestId); @@ -905,6 +964,29 @@ void ProfilerPlugin::createMetadata(ThrowStatusExceptionWrapper* status, RefPtr< "grant select, update, insert, delete on table plg$prof_statements to plg$profiler", + R"""( + create table plg$prof_cursors ( + profile_id bigint not null + constraint plg$prof_cursors_session_fk + references plg$prof_sessions + on delete cascade + using index plg$prof_cursors_profile, + statement_id bigint not null, + cursor_id integer not null, + name char(63) character set utf8, + line_num integer, + column_num integer, + constraint plg$prof_cursors_pk + primary key (profile_id, statement_id, cursor_id) + using index plg$prof_cursors_profile_statement_cursor, + constraint plg$prof_cursors_statement_fk + foreign key (profile_id, statement_id) references plg$prof_statements + on delete cascade + using index plg$prof_cursors_profile_statement + ))""", + + "grant select, update, insert, delete on table plg$prof_cursors to plg$profiler", + R"""( create table plg$prof_record_sources ( profile_id bigint not null @@ -924,6 +1006,10 @@ void ProfilerPlugin::createMetadata(ThrowStatusExceptionWrapper* status, RefPtr< foreign key (profile_id, statement_id) references plg$prof_statements on delete cascade using index plg$prof_record_sources_profile_statement, + constraint plg$prof_record_sources_cursor_fk + foreign key (profile_id, statement_id, cursor_id) references plg$prof_cursors + on delete cascade + using index plg$prof_record_sources_profile_statement_cursor, constraint plg$prof_record_sources_parent_record_source_fk foreign key (profile_id, statement_id, cursor_id, parent_record_source_id) references plg$prof_record_sources (profile_id, statement_id, cursor_id, record_source_id) @@ -1021,6 +1107,10 @@ void ProfilerPlugin::createMetadata(ThrowStatusExceptionWrapper* status, RefPtr< foreign key (profile_id, statement_id) references plg$prof_statements on delete cascade using index plg$prof_record_source_stats_profile_statement, + constraint plg$prof_record_source_stats_cursor_fk + foreign key (profile_id, statement_id, cursor_id) references plg$prof_cursors + on delete cascade + using index plg$prof_record_source_stats_statement_cursor, constraint plg$prof_record_source_stats_record_source_fk foreign key (profile_id, statement_id, cursor_id, record_source_id) references plg$prof_record_sources on delete cascade @@ -1132,6 +1222,9 @@ void ProfilerPlugin::createMetadata(ThrowStatusExceptionWrapper* status, RefPtr< 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.access_path, @@ -1147,6 +1240,10 @@ void ProfilerPlugin::createMetadata(ThrowStatusExceptionWrapper* status, RefPtr< 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 @@ -1167,6 +1264,9 @@ void ProfilerPlugin::createMetadata(ThrowStatusExceptionWrapper* status, RefPtr< 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.access_path @@ -1277,6 +1377,19 @@ void Session::defineStatement(ThrowStatusExceptionWrapper* status, SINT64 statem statement->sqlText = sqlText; } +void Session::defineCursor(SINT64 statementId, unsigned cursorId, const char* name, unsigned line, unsigned column) +{ + const auto cursor = cursors.put({statementId, cursorId}); + fb_assert(cursor); + + if (!cursor) + return; + + cursor->name = name; + cursor->line = line; + cursor->column = column; +} + void Session::defineRecordSource(SINT64 statementId, unsigned cursorId, unsigned recSourceId, const char* accessPath, unsigned parentRecordSourceId) {