How to profile PL/SQL code

Oracle PL/SQL engine provides us with pretty cool tools for profiling our code. There are two profilers (that I know of): DBMS_PROFILER и DBMS_HPROF. H is for Hierarchical here, while DBMS_PROFILER is ‘flat’ (does not consider function stack).

I’m gonna cover the DBMS_PROFILER but working with the DBMS_HPROF is pretty much the same.

This is a repost from my old blog that is now lost to time and carelessness. I found some of my drafts and reedited a handful that might be interesting to read.

So you might have read it before but it's not like I have a massive following so you probably haven't ;)

Environment

Setting up the environment is easy. Run the built-in script from $ORACLE_HOME/rdbms/admin/proftab.sql. It’s gonna create a few tables and a sequence.

The profiler will populate them so let’s take a look at what they are.

PLSQL_PROFILER_RUNS

Every time you start a profiler it creates a ‘run’. Every run has an id, and it’s basically a scope for all the data the profiler is gathering.

Thanks to this you can keep your previous metrics when running the profiler again after you’ve made an edit and see what changed between runs. You can even give meaningful names to runs. Cool, huh?

PLSQL_PROFILER_UNITS

Every PL/SQL unit that was called during a run will show up here. That can be a package, a function/procedure or an anonymous block.

PLSQL_PROFILER_DATA

This is the meat of our profiling session: all the metrics go here. How many times was a unit called, how much time it took, etc.

Profiling

You can only profile your own session, so you need to be able to run the code you’re going to profile. To start profiling just wrap your code in this.

BEGIN
DBMS_PROFILER.START_PROFILER;

... your code ...

DBMS_PROFILER.STOP_PROFILER;

END;

START_PROFILER accepts an optional argument that you can use to identify a specific run later to see how your results change from run to run.

Interpreting results

Some SQL IDEs come with built-in tools to view the profiling results (ahem, PL/SQL Developer, ahem). But if you don’t use those (god I hope not, it’s 2014). Here’s a helpful query that you can adapt to your task.

SELECT U.UNIT_OWNER||'.'||UNIT_NAME                       UNIT,            -- PL/SQL block
       D.LINE#,                                                            -- Line no
       D.TOTAL_OCCUR,                                                      -- How many times?
       D.TOTAL_TIME,                                                       -- Total time
       ROUND(D.TOTAL_TIME/D.TOTAL_OCCUR,2)                AVG_TIME,        -- Average time
       ROUND(D.TOTAL_TIME/R.RUN_TOTAL_TIME,2)*100 ||'%'   PART_IN_OVERALL, -- Percentage of total time
       D.MAX_TIME,                                                         -- Max time
       D.MIN_TIME,                                                         -- Min time
       U.UNIT_TYPE,                                                        -- Unit type
       R.RUNID,                                                            -- ID of a run
       U.UNIT_NUMBER                                                       -- ID of a unit
FROM   PLSQL_PROFILER_DATA  D, -- Code blocks
       PLSQL_PROFILER_UNITS U, -- Packages and procedures
       PLSQL_PROFILER_RUNS  R  -- Runs
WHERE  U.UNIT_NUMBER = D.UNIT_NUMBER
AND    R.RUNID = D.RUNID
AND    D.TOTAL_OCCUR > 0
AND    D.TOTAL_TIME > 0
ORDER BY D.TOTAL_TIME/D.TOTAL_OCCUR DESC,  -- average execution time of a code block
         D.TOTAL_TIME               DESC   -- total execution time of a code block