This package enables the collection of data for performance improvement or for determining code coverage for PL/SQL applications. Application developers can use code coverage data to focus their incremental testing efforts. This blog provides DBAs, developers, support analysts, and consultants with PL/SQL tuning hints and tips using profiling.

With DBMS_PROFILER package, you can generate profiling information for all named library units that are executed in a session. The profiler gathers information at the PL/SQL virtual machine level. This information includes the total number of times each line has been executed, the total amount of time that has been spent executing that line, and the minimum and maximum times that have been spent on a particular execution of that line.

Installing the DBMS_PROFILER package

A DBA or developer can profile an application simply by calling the DBMS_PROFILER package calls to start and stop the profiling process.

The DBMS_PROFILER package should already be installed. A simple test is to run a describe of the package as SYSDBA. If the package is not installed then the following output will be displayed:

SQL> DESC DBMS_PROFILER
ERROR:
ORA-04043: object DBMS_PROFILER does not exist

The DBMS_PROFILER package can be installed by running the profload.sql script as SYSDBA. This script can be found in the $ORACLE_HOME/rdbms/admin directory:

SQL> @?/rdbms/admin/profload.sql

The following output indicates that the package is installed:

SQL> desc dbms_profiler
FUNCTION FLUSH_DATA RETURNS BINARY_INTEGER
PROCEDURE FLUSH_DATA
PROCEDURE GET_VERSION
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 MAJOR                          BINARY_INTEGER          OUT
 MINOR                          BINARY_INTEGER          OUT
FUNCTION INTERNAL_VERSION_CHECK RETURNS BINARY_INTEGER
FUNCTION PAUSE_PROFILER RETURNS BINARY_INTEGER
PROCEDURE PAUSE_PROFILER
FUNCTION RESUME_PROFILER RETURNS BINARY_INTEGER
PROCEDURE RESUME_PROFILER
PROCEDURE ROLLUP_RUN
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 RUN_NUMBER                     NUMBER                  IN
PROCEDURE ROLLUP_UNIT
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 RUN_NUMBER                     NUMBER                  IN
 UNIT                           NUMBER                  IN
FUNCTION START_PROFILER RETURNS BINARY_INTEGER
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 RUN_COMMENT                    VARCHAR2                IN     DEFAULT
 RUN_COMMENT1                   VARCHAR2                IN     DEFAULT
 RUN_NUMBER                     BINARY_INTEGER          OUT
PROCEDURE START_PROFILER
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 RUN_COMMENT                    VARCHAR2                IN     DEFAULT
 RUN_COMMENT1                   VARCHAR2                IN     DEFAULT
 RUN_NUMBER                     BINARY_INTEGER          OUT
FUNCTION START_PROFILER RETURNS BINARY_INTEGER
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 RUN_COMMENT                    VARCHAR2                IN     DEFAULT
 RUN_COMMENT1                   VARCHAR2                IN     DEFAULT
PROCEDURE START_PROFILER
 Argument Name                  Type                    In/Out Default?
 ------------------------------ ----------------------- ------ --------
 RUN_COMMENT                    VARCHAR2                IN     DEFAULT
 RUN_COMMENT1                   VARCHAR2                IN     DEFAULT
FUNCTION STOP_PROFILER RETURNS BINARY_INTEGER
PROCEDURE STOP_PROFILER

The tables and sequence can be created in the schema for each user who wants to gather profiler data or alternately these tables can be created in a central schema. For this example, we will run everything in a new created TEST schema. (Please see the code sample for the creation statements.) The SQL script that creates the required objects is also found in the $ORACLE_HOME/rdbms/admin directory within the proftab.sql file.

The test code

The following examples need select privilege granted on sys.v_$timer dynamic view. For example, execute the following to grant access to TEST user ID:

SQL> GRANT SELECT ON SYS.V_$TIMER TO TEST;

Grant succeeded.

Setup the environment within the TEST schema using SQL*Plus:

SQL> CONN test/test
Connected.
SQL> @?/rdbms/admin/proftab.sql
drop table plsql_profiler_data cascade constraints
           *
ERROR at line 1:
ORA-00942: table or view does not exist

drop table plsql_profiler_units cascade constraints
           *
ERROR at line 1:
ORA-00942: table or view does not exist

drop table plsql_profiler_runs cascade constraints
           *
ERROR at line 1:
ORA-00942: table or view does not exist

drop sequence plsql_profiler_runnumber
              *
ERROR at line 1:
ORA-02289: sequence does not exist

Table created.

Comment created.

Table created.

Comment created.

Table created.

Comment created.

Sequence created.

SQL> CREATE TABLE test_table
  2    (n_number int,
  3     n_size varchar2(5));

Table created.

SQL> CREATE OR REPLACE PROCEDURE set_size
  2    (n_number IN number, n_size OUT varchar2) AS
  3  BEGIN
  4    IF
  5       n_number > 100
  6    THEN
  7       n_size := 'Large';
  8    ELSE
  9       n_size := 'Small';
 10    END IF;
 11  END set_size;
 12  /

Procedure created.

SQL> CREATE OR REPLACE PACKAGE test AS
  2    PROCEDURE main (n_number IN number, n_size OUT varchar2);
  3  END;
  4  /

Package created.

SQL> CREATE OR REPLACE PACKAGE BODY test AS
  2    PROCEDURE dummy_proc (dept# IN number, count# out number) AS
  3  BEGIN
  4    SELECT round(DBMS_Random.Value(1,200)) INTO count# from dual;
  5  END dummy_proc;
  6  
  7  PROCEDURE main (n_number IN number, n_size OUT varchar2) IS
  8    count# number;
  9    start_time number;
 10    end_time number;
 11  BEGIN
 12    select hsecs into start_time from v$timer;
 13    set_size(n_number, n_size);
 14    FOR i IN 1 .. 200 LOOP
 15       dummy_proc( i, count#);
 16    END LOOP; -- e loop
 17    -- Placeholder
 18    select hsecs into end_time from v$timer;
 19  END main;
 20  END test;
 21  /

Package body created.

SQL> CREATE OR REPLACE TRIGGER trig_bi BEFORE INSERT
  2  ON test_table FOR EACH ROW
  3  BEGIN
  4    test.main(:new.n_number, :new.n_size);
  5  END;
  6  /

Trigger created.

Once environment has been set, start the profiling process and insert a row into the TEST_TABLE table to execute the above code and generate some trace information.

The following code turns on profiling, executes an insert then turns off profiling:

SQL> SELECT DBMS_PROFILER.START_PROFILER 
  2     ('TEST: '||to_char(sysdate,'dd-Mon-YYYY hh:mi:ss')) "Return Value" 
  3    FROM dual;

Return Value
------------
           0

SQL> INSERT INTO test_table (n_number) VALUES (85) ;

1 row created.

SQL> COMMIT;

Commit complete.

SQL> SELECT DBMS_PROFILER.STOP_PROFILER "Return Value" FROM dual;

Return Value
------------
0

Note: Return value may vary on your database.

Viewing the Profiling Data

There are two steps to compiling the profile information. Step 1 is to find the RunID and Step 2 is to use the RunID within subsequent queries to gather associated information.

1 – Find the RunID

To review profile results, first find the runid of the given test using the following command. The “column” command used below in SQL*Plus simply formats the data making it easier to read.

SQL> COLUMN run_comment FORMAT a40
SQL> SELECT runid, run_date, run_comment FROM plsql_profiler_runs;

     RUNID RUN_DATE  RUN_COMMENT
---------- --------- ----------------------------------------
1 13-FEB-13 TEST: 13-Feb-2013 05:35:09

Although there may exist multiple entries, based on the timestamp it can easily be determined which was the last execution. In this case, there is just one execution and it is RUNID 1.

2 – Use the RunID to gather additional information.

To concentrate on timing details (min, max, avg), use a simple query that incorporates the RunID just obtained.

If no data is returned, there is a chance the RunID within the following SQL was not changed. The “column” command used below in SQL*Plus simply formats the data making it easier to read.

SQL> COLUMN unit_name FORMAT a15
SQL> COLUMN occured FORMAT 999999
SQL> COLUMN line# FORMAT 99999
SQL> COLUMN tot_time FORMAT 999.999999
SQL> SET PAGESIZE 100
SQL> SELECT u.unit_name,
  2        d.line#,
  3        d.total_occur occured,
  4        (d.total_time/1000000000) tot_time,
  5        (d.min_time/1000000000) min_time,
  6        (d.max_time/1000000000) max_time
  7   FROM
  8        plsql_profiler_units u,
  9        plsql_profiler_data d
 10  WHERE  d.runid = u.runid
 11    AND  d.unit_number = u.unit_number
 12    AND  d.total_occur > 0
 13    AND  u.runid = &RUNID
 14  ORDER BY u.unit_number, d.line# ;
Enter value for runid: 1
old  13:  and u.runid = &RUNID 
new  13:  and u.runid = 1

UNIT_NAME        LINE# OCCURED    TOT_TIME   MIN_TIME   MAX_TIME
--------------- ------ ------- ----------- ---------- ----------
TRIG_BI              2       1     .000058 .000003879  .00005431
TRIG_BI              3       1     .000002 .000001939 .000001939
TEST                 4     199     .011660 .000056249 .000133835
TEST                 5     200     .000350 .000000969 .000002909
TEST                 7       1     .000004 .000000969 .000002909
TEST                12       1     .001355 .001354841 .001354841
TEST                13       1     .000031 .000001939 .000029094
TEST                14     201     .000289 .000000969 .000001939
TEST                15     200     .000400 .000001939 .000002909
TEST                18       1     .000109  .00010862  .00010862
TEST                19       1     .000003 .000002909 .000002909
SET_SIZE             5       1     .000002 .000001939 .000001939
SET_SIZE             9       1     .000004 .000003879 .000003879
SET_SIZE            11       1     .000002 .000001939 .000001939

14 rows selected.

To see the source code line, issue the following command using a query that incorporates the RunID just obtained.

SQL> COLUMN unit_name FORMAT a10
SQL> COLUMN line# FORMAT 99999
SQL> COLUMN text FORMAT a65
SQL> SET PAGESIZE 100
SQL> SET LINESIZE 100
SQL> SELECT p.unit_name,
  2        p.line# line,
  3        substr(s.text, 1,75) text
  4  FROM
  5        (
  6           SELECT u.unit_name,
  7                  d.total_occur occured,
  8                  (d.total_time/1000000000) tot_time,
  9                  d.line#
 10             FROM
 11                  plsql_profiler_units u,
 12                  plsql_profiler_data d
 13            WHERE
 14                  d.runid = u.runid
 15              AND d.unit_number = u.unit_number
 16              AND d.total_occur > 0
 17              AND u.runid = &RUNID
 18         ) p, user_source s
 19  WHERE
 20        p.unit_name = s.name(+)
 21    AND  p.line# = s.line (+)
 22  ORDER BY p.unit_name, p.line# ;
Enter value for runid: 1
old  17:      AND u.runid = &RUNID
new  17:      AND u.runid = 1

UNIT_NAME LINE TEXT
--------- ---- ---------------------------------------------------------------
SET_SIZE     5   n_number > 100
SET_SIZE     9   n_size := 'Small';
SET_SIZE    11 END set_size;
TEST         4   SELECT round(DBMS_Random.Value(1,200)) INTO count# FROM dual;
TEST         5 END dummy_proc;
TEST         7 PROCEDURE main (n_number IN number, n_size OUT varchar2) IS
TEST        12    select hsecs into start_time from v$timer;
TEST        13    set_size(n_number, n_size);
TEST        14    FOR i IN 1 .. 200 LOOP
TEST        15   dummy_proc( i, count#);
TEST        18    select hsecs into end_time from v$timer;
TEST        19 END main;
TRIG_BI      2 ON test_table FROM EACH ROW
TRIG_BI      3 BEGIN

14 rows selected.

Comments on the above output

– The time is measured in 1/1000000000 seconds.

– The time of the procedure call is only the overhead of the call and return, not the execution time of the entire procedure. For example, the TRIG_BI is a call of TEST.main, and its time is less than that of the FOR loop in the main procedure.

– The FOR loop is executed 200 times. Therefore, the header line is executed 200+1 times.

Link to script that contains the examples in this post.

 

 

 

 

 

 

 

Comment