MINI MINI MANI MO

Path : /opt/oracle/product/18c/dbhomeXE/rdbms/admin/
File Upload :
Current File : //opt/oracle/product/18c/dbhomeXE/rdbms/admin/utlspadv.sql

Rem
Rem $Header: rdbms/admin/utlspadv.sql /main/28 2017/07/31 15:02:02 jorgrive Exp $
Rem
Rem utlspadv.sql
Rem
Rem Copyright (c) 2007, 2017, Oracle and/or its affiliates. 
Rem All rights reserved.
Rem
Rem    NAME
Rem      utlspadv.sql - Streams Performance ADVisor Utility
Rem
Rem    DESCRIPTION
Rem      A utility package for collecting streams topology and statistics,
Rem      which are similar to what Strmmon produces.
Rem
Rem    NOTES
Rem      <other useful comments, qualifications, etc.>
Rem
Rem    BEGIN SQL_FILE_METADATA
Rem    SQL_SOURCE_FILE: rdbms/admin/utlspadv.sql
Rem    SQL_SHIPPED_FILE: rdbms/admin/utlspadv.sql
Rem    SQL_PHASE: UTILITY
Rem    SQL_STARTUP_MODE: NORMAL
Rem    SQL_IGNORABLE_ERRORS: NONE
Rem    END SQL_FILE_METADATA
Rem    
Rem    MODIFIED   (MM/DD/YY)
Rem    jorgrive    07/12/17 - Bugs 26370185, 26370217 compilation warnings
Rem    sbaez       04/28/15 - Bug 20777109 - LIDENT: tbl_name needs 128 bytes
Rem    p4kumar     03/15/13 - Bug 14307048 fix
Rem    myuin       12/26/12 - XbranchMerge myuin_bug-15981425_12.1.0.1.0 from
Rem                           st_rdbms_12.1.0.1
Rem    myuin       12/16/12 - bug 15981425: Add Extract/Replicat components to
Rem                           SPADV output
Rem    traney      04/05/11 - 35209: long identifiers dictionary upgrade
Rem    vchandar    12/25/10 - Bug 10384221
Rem    vchandar    06/30/10 - Account for all component/subcomponent types
Rem    vchandar    06/21/10 - use utl_file
Rem    vchandar    05/17/10 - lrg 4527973
Rem    vchandar    02/12/10 - add secondary index for comp_stat table
Rem    vchandar    02/04/10 - Handle multiple bottlenecks for a single runid
Rem    bpwang      02/01/10 - Bug 9256726: Handle component state
Rem    vchandar    12/13/09 - Pass html bottleneck info in spare3
Rem    vchandar    12/11/09 - Remove usage of htp package
Rem    vchandar    12/10/09 - make save_comp_stat default TRUE
Rem    vchandar    09/08/09 - Adding support for html report
Rem    jinwu       06/19/09 - allow purge in stop_monitoring
Rem    jinwu       04/21/09 - fix bug 7508507 (reduce sharable memory use)
Rem    jinwu       09/09/08 - add show_stats_table in streams$_pa_monitoring
Rem    jinwu       09/09/08 - donot persist streams$_pa_show_comp_stat
Rem    jinwu       09/08/08 - add param_unit to streams$_pa_control
Rem    jinwu       09/08/08 - add table streams$_pa_database_prop
Rem    tianli      09/15/08 - add spadv support for xstream
Rem    jinwu       08/18/08 - collect stats for active component only
Rem    jinwu       07/21/08 - add is/start/alter/stop_monitoring
Rem    jinwu       04/08/08 - change show_cca_mode to show_optimization
Rem    jinwu       04/04/08 - use scientific representation for BANDWIDTH
Rem    jinwu       04/04/08 - remove column original_path_id and active
Rem    jinwu       02/08/08 - change ANR to PS+PR for local CCAC
Rem    jinwu       12/31/07 - add CAP+PS for remote CCA
Rem    jinwu       12/31/07 - add cca_mode
Rem    jinwu       10/19/07 - fix ####### in percentage
Rem    jinwu       08/15/07 - fix bug 6343077
Rem    jinwu       07/06/07 - aggregate TOP EVENT percentage for APS and LMP
Rem    jinwu       07/05/07 - rename 'CAPTURE PROCESS' to 'CAPTURE SESSION'
Rem    jinwu       06/13/07 - change param stat_table to path_stat_table.
Rem    jinwu       06/05/07 - Created
Rem

-- set up some environment for html report
-- this should probably be moved to whichever script loads utlspadv
-- html tags have &
set define off;

-- Create tables for monitoring API
drop table streams$_pa_monitoring;
create table streams$_pa_monitoring
(
  job_name           varchar2(128) not null,
  client_name        varchar2(128) default null,
  query_user_name    varchar2(128) default null,
  show_stats_table   varchar2(128) default 'STREAMS$_PA_SHOW_PATH_STAT',
  started_time       timestamp default null,
  stopped_time       timestamp default null,
  altered_time       timestamp default null,
  state              varchar2(30) default null           /* ENABLED, STOPPED */
)
/

-- PROP_NAME:
--   VERSION
--   COMPATIBILITY
--   MANAGEMENT_PACK_ACCESS
--   DB_UNIQUE_NAME
drop table streams$_pa_database;
create table streams$_pa_database
(
  global_name        varchar2(128) not null,
  last_queried       date default null,
  error_number       number default null,
  error_message      varchar2(4000) default null
)
/

drop table streams$_pa_database_prop;
create table streams$_pa_database_prop
(
  global_name        varchar2(128) not null,
  prop_name          varchar2(128),
  prop_value         varchar2(128)
)
/

drop table streams$_pa_component;
create table streams$_pa_component
(
  component_id       number not null,
  component_name     varchar2(194),
  component_db       varchar2(128),
  component_type     varchar2(20),          /* type of the streams component */
                                                     /*              CAPTURE */
                                                     /*   PROPAGATION SENDER */
                                                     /* PROPAGATION RECEIVER */
                                                     /*                APPLY */
                                                     /*                QUEUE */
  component_changed_time date,   /* time that the component was last changed */
  spare1             number,                               /* spare column 1 */
  spare2             number,                               /* spare column 2 */
  spare3             varchar2(4000),                       /* spare column 3 */
  spare4             date                                  /* spare column 4 */
)
/

-- PROP_NAME: (CAPTURE) SOURCE_DATABASE, PARALLELISM, OPTIMIZATION_MODE
--            ( APPLY ) SOURCE_DATABASE, PARALLELISM, APPLY_CAPTURED,
--                      MESSAGE_DELIVERY_MODE
drop table streams$_pa_component_prop;
create table streams$_pa_component_prop
(
  component_id   number not null,                     /* id of the component */
  prop_name      varchar2(128),                      /* name of the property */
  prop_value     varchar2(4000),                    /* value of the property */
  spare1         number,
  spare2         number,
  spare3         varchar2(4000),
  spare4         date
)
/

drop table streams$_pa_component_link;
create table streams$_pa_component_link
(
  path_id             number not null,
  path_key            varchar2(4000),           /* unique key to stream path */
  source_component_id number not null,
  destination_component_id number not null,
  position            number, /* 1-based position of the link on stream path */
  spare1              number,                              /* spare column 1 */
  spare2              number,                              /* spare column 2 */
  spare3              varchar2(4000),                      /* spare column 3 */
  spare4              date                                 /* spare column 4 */
)
/

-- PARAM_NAME:
--   INTERVAL (default 60 seconds)
--   RETENTION_TIME (default 24 hours)
--   TOP_EVENT_THRESHOLD (default 15)
--   BOTTLENECK_IDLE_THRESHOLD (default 50)
--   BOTTLENECK_FLOWCTRL_THRESHOLD (default 50)

drop table streams$_pa_control;
create table streams$_pa_control
(
  advisor_run_id     number,        /* 1-based logical number of advisor run */
  advisor_run_time   date,                  /* time that the advisor was run */
  param_name         varchar2(128),
  param_value        varchar2(4000),
  param_unit         varchar2(128), /* unit name? */
  spare1             number,                               /* spare column 1 */
  spare2             number,                               /* spare column 2 */
  spare3             varchar2(4000),                       /* spare column 3 */
  spare4             date                                  /* spare column 4 */
)
/

drop table streams$_pa_component_stat;
create table streams$_pa_component_stat
(
  advisor_run_id   number,          /* 1-based logical number of advisor run */
  advisor_run_time date,                    /* time that the advisor was run */
  component_id     number,                            /* id of the component */
  statistic_time   date,                /* time that the statistic was taken */
  statistic_name   varchar2(64), /* name of the statistic. arbitrary length */
  statistic_value  number,                         /* value of the statistic */
  statistic_unit   varchar2(64), /* unit of the statistic. arbitrary length */
  sub_component_type varchar2(64) default null,     /* type of sub-component */
  session_id       number default null,                 /* id of the session */
  session_serial#  number default null,            /* serial# of the session */
  spare1           number,
  spare2           number,
  spare3           varchar2(4000),
  spare4           date
)
/

-- statistic_name: LATENCY
--                 THROUGHPUT

/* APPLY_DATABASE_TIMESTAMP is stored as statistic */
drop table streams$_pa_path_stat;
create table streams$_pa_path_stat
(
  advisor_run_id   number,          /* 1-based logical number of advisor run */
  advisor_run_time date,                    /* time that the advisor was run */
  path_id          number,                                  /* id the stream */
  path_key         varchar2(4000),              /* unique key to stream path */
  statistic_time   date,                /* time that the statistic was taken */
  statistic_name   varchar2(64), /* name of the statistic. arbitrary length */
  statistic_value  number,                         /* value of the statistic */
  statistic_unit   varchar2(64), /* unit of the statistic. arbitrary length */
  spare1           number,
  spare2           number,
  spare3           varchar2(4000),
  spare4           date
)
/

drop table streams$_pa_path_bottleneck;
create table streams$_pa_path_bottleneck
(
  advisor_run_id      number,       /* 1-based logical number of advisor run */
  advisor_run_time    date,                 /* time that the advisor was run */
  advisor_run_reason  varchar2(4000),       /* reason for bottleneck results */
  path_id             number,                               /* id the stream */
  path_key            varchar2(4000),           /* unique key to stream path */
  component_id        number,                         /* id of the component */
  top_session_id      number,             /* top session id of the component */
  top_session_serial# number,  /* top session serial number of the component */
  action_name         varchar2(130),  /* the action name for the top session */
  bottleneck_identified varchar2(128),  /* whether bottleneck was identified */
  spare1              number,
  spare2              number,
  spare3              varchar2(4000),
  spare4              date
)
/

drop table streams$_pa_show_comp_stat;
create table streams$_pa_show_comp_stat(
  advisor_run_id     number,
  advisor_run_time   date,
  path_id            number,
  position           number,
  component_id       number,
  component_name     varchar2(194),
  component_type     varchar2(30),
  sub_component_type varchar2(30),
  session_id         number,
  session_serial#    number,
  statistic_alias    varchar2(30),
  statistic_name     varchar2(64),
  statistic_value    number,
  statistic_unit     varchar2(64)
)
/

CREATE INDEX comp_stat_pkey ON streams$_pa_show_comp_stat
                               (advisor_run_id, path_id, position, 
                                statistic_alias)
/

drop table streams$_pa_show_path_stat;
create table streams$_pa_show_path_stat(
  path_id            number,
  advisor_run_id     number,
  advisor_run_time   date,  
  setting            varchar2(2000),
  statistics         varchar2(4000),
  session_statistics varchar2(4000),
  optimization       number
)
/


-- Streams Performance Advisor Utility Package.
create or replace package UTL_SPADV authid current_user as
  -- Package version
  VERSION CONSTANT VARCHAR2(30) := '2.0';

  ----------------------------------------------------------------------------=
  -- SHOW_STATS
  --   Print statistics for a stream path.
  ----------------------------------------------------------------------------=
  procedure SHOW_STATS(
    path_stat_table   in varchar2 default 'STREAMS$_ADVISOR_PATH_STAT',
    path_id           in number   default null,  -- show all stream paths
    bgn_run_id        in number   default -1,    -- show the last 10 runs
    end_run_id        in number   default -10,
    show_path_id      in boolean  default TRUE,
    show_run_id       in boolean  default TRUE,
    show_run_time     in boolean  default TRUE,
    show_optimization in boolean  default TRUE,
    show_setting      in boolean  default FALSE,
    show_stat         in boolean  default TRUE,
    show_sess         in boolean  default FALSE,
    show_legend       in boolean  default TRUE);

  ----------------------------------------------------------------------------=
  -- COLLECT_STATS
  --   Collect statistics for all active stream paths.
  ----------------------------------------------------------------------------=
  procedure COLLECT_STATS(
    interval                  in number   default 60,
    num_runs                  in number   default 10,
    comp_stat_table           in varchar2 default 'STREAMS$_ADVISOR_COMP_STAT',
    path_stat_table           in varchar2 default 'STREAMS$_ADVISOR_PATH_STAT',
    top_event_threshold       in number   default 15,
    bottleneck_idle_threshold in number   default 50,
    bottleneck_flowctrl_threshold in number default 50);

  ----------------------------------------------------------------------------=
  -- IS_MONITORING
  --   Checks if a client has submitted a monitoring job.
  --
  ----------------------------------------------------------------------------=
  function IS_MONITORING(
    job_name    IN VARCHAR2 DEFAULT 'STREAMS$_MONITORING_JOB',
    client_name IN VARCHAR2 DEFAULT NULL) return BOOLEAN;

  ----------------------------------------------------------------------------=
  -- START_MONITORING
  --   Begins persistent monitoring of Streams performance.
  --   Allows (1) at most one monitoring job per schema, and
  --          (2) at most one EM monitoring job per database.
  --
  -- Currently we require the following from the invoking user:
  -- 1. Database links to all participating Streams databases
  -- 2. Privilege to run the analyze_current_performance
  -- 3. Enough space to store monitoring results in tables
  -- 4. Privilege to create a job
  -- 
  -- Parameters:
  --   job_name:         The name of the job to create.
  --   client_name:      The name of the client
  --   query_user_name:  Privileges will be granted to this user to query 
  --                     the result tables.
  --   interval:         The frequency of monitoring in seconds, up to a 
  --                     maximum of 3600 seconds. 
  --   top_event_threshold:
  --                     The percentage of time over which an
  --                     event will be classified as a top event.
  --   bottleneck_idle_threshold:
  --   bottleneck_flowctrl_threshold:
  --                     The thresholds above which a given compenent
  --                     will not be considered a bottleneck.
  --   retention_time:   The number of hours to persist results.
  --
  -- Errors:
  --   ORA-XXXXX:        Monitoring already started. If for example you want 
  --                     to change the user that is monitoring, first call 
  --                     stop_monitoring, then call start_monitoring with 
  --                     the new user name.
  --   ORA-20111:
  --     'cannot start monitoring due to active EM monitoring job'
  --   ORA-20112:
  --     'cannot start monitoring due to active Streams monitoring job'
  --
  procedure START_MONITORING(
    job_name                     IN VARCHAR2 DEFAULT 'STREAMS$_MONITORING_JOB',
    client_name                  IN VARCHAR2 DEFAULT NULL,
    query_user_name              IN VARCHAR2 DEFAULT NULL,
    interval                     IN NUMBER DEFAULT 60,
    top_event_threshold          IN NUMBER DEFAULT 15,
    bottleneck_idle_threshold    IN NUMBER DEFAULT 50,
    bottleneck_flowctrl_threshold IN NUMBER DEFAULT 50,
    retention_time                IN NUMBER DEFAULT 24);

  ----------------------------------------------------------------------------=
  -- ALTER_MONITORING
  --   Alters monitoring of Streams performance
  --
  -- Parameters:
  --   interval:         The frequency of monitoring in seconds, up to a 
  --                     maximum of 3600 seconds. 
  --   top_event_threshold:  
  --                     The percentage of time over which an
  --                     event will be classified as a top event. 
  --   bottleneck_idle_threshold:  
  --   bottleneck_flowctrl_threshold:  
  --                     The thresholds above which a given compenent
  --                     will not be considered a bottleneck.
  --   retention_time:   The number of hours to persist results. 
  --
  -- Errors:
  --   ORA-20113: 'no active monitoring job found'
  --
  procedure ALTER_MONITORING(
    interval                      IN NUMBER DEFAULT null,
    top_event_threshold           IN NUMBER DEFAULT null,
    bottleneck_idle_threshold     IN NUMBER DEFAULT null,
    bottleneck_flowctrl_threshold IN NUMBER DEFAULT null,
    retention_time                IN NUMBER DEFAULT null);

  ----------------------------------------------------------------------------=
  -- STOP_MONITORING
  --   Stops persistent monitoring of Streams performance.
  --
  -- Parameters:
  --   purge:      Whether or not to purge monitoring results from disk
  --
  -- Returns:
  --   TRUE if monitoring has been enabled, false otherwise
  --
  -- Errors:
  --   ORA-20113: 'no active monitoring job found'
  procedure STOP_MONITORING(purge IN BOOLEAN DEFAULT FALSE);

  ----------------------------------------------------------------------------=
  -- SHOW_STATS_HTML
  --    generates a html report of the streams performance statistics 
  --    collected using collect_stats
  --
  -- Parameters :
  --   directory       :   directory object name to place the html report
  --   reportName      :   name of the report file to be generated
  --   comp_stat_table :   the comp_stat_tbl used in the previous call to
  --                       collect_stats
  --   path_id         :   path for which statistics needs to be generated
  --   bgn_run_id      :   start run id to generate statistics
  --   end_run_id      :   end run id to generate statistics
  --   detailed        :   TRUE generates run level/ component level 
  --                       statistics also
  --   Print statistics for a stream path.
  ----------------------------------------------------------------------------=
  procedure SHOW_STATS_HTML(
    directory         in varchar2,
    reportName        in varchar2 default 'SPADVREPORT.HTML',
    comp_stat_table   in varchar2 default 'STREAMS$_ADVISOR_COMP_STAT',
    path_id           in number   default null,  -- show all stream paths
    bgn_run_id        in number   default -1,    -- show the last 10 runs
    end_run_id        in number   default -10,
    detailed          in boolean  default TRUE
    );

end UTL_SPADV;
/
show errors;


create or replace package body UTL_SPADV as

  -- Constants about date, time
  minutes_per_day  CONSTANT NUMBER := 1440;
  seconds_per_day  CONSTANT NUMBER := 86400;
  seconds_per_hour CONSTANT NUMBER := 3600;

  -- Streams Advisor control parameters
  param_interval               NUMBER := 60;  -- in seconds
  param_retention_time         NUMBER := 24;  -- in hours
  param_top_event_threshold    NUMBER := 15;
  param_bot_idle_threshold     NUMBER := 50;
  param_bot_flowctrl_threshold NUMBER := 50;
  total_param_cnt              NUMBER := 5;

  monitoring_job_name        dbms_id := null;
  monitoring_client_name     dbms_id := null;
  monitoring_query_user_name dbms_id := null;
  monitoring_started_time    TIMESTAMP    := null;
  -- Timestamp that the monitoring job was last altered
  monitoring_altered_time    TIMESTAMP    := null;

  ----------------------------------------------------------------------------=
  -- The STATISTIC_ALIAS in STAT_TYPE and STREAMS$_ADVISOR_COMP_STAT can have
  -- the following values: 'S1', 'S2', 'S3', 'S4', 'S5', 'S6' and 'S7'. These
  -- numbered values are sorted to order stream component statistics into the
  -- single-line representation.
  --
  -- CAPTURE:
  --   'S1'   <msgs captured/sec>                                 CAPTURE RATE
  --   'S2'   <msgs enqueued/sec>                                 ENQUEUE RATE
  --   'S3'   <latency>                                                LATENCY
  --
  -- CAPTURE SUB-COMPONENT LEVEL:
  --   'S4'   <LMP parallelism>
  --   'S5'   <idl%>                                                      IDLE
  --   'S6'   <flwctrl%>                                          FLOW CONTROL
  --   'S7'   <topevt%>                                               EVENT: %
  --
  -- PROPAGATION SENDER:
  --   'S1'   <msgs sent/sec>                                        SEND RATE
  --   'S2'   <bytes sent/sec>                                       BANDWIDTH
  --   'S3'   <latency>                                                LATENCY
  --   'S4'   NA
  --   'S5'   <idl%>                                                      IDLE
  --   'S6'   <flwctrl%>                                          FLOW CONTROL
  --   'S7'   <topevt%>                                               EVENT: %
  --
  -- PROPAGATION RECEIVER:
  --   'S5'   <idl%>                                                      IDLE
  --   'S6'   <flwctrl%>                                          FLOW CONTROL
  --   'S7'   <topevt%>                                               EVENT: %
  --
  -- QUEUE:
  --   'S1'   <msgs enqueued/sec>                                 ENQUEUE RATE
  --   'S2'   <msgs spilled/sec>                                    SPILL RATE
  --   'S3'   <msgs in queue>                               CURRENT QUEUE SIZE
  --
  -- APPLY:
  --   'S1'   <msgs applied/sec>                            MESSAGE APPLY RATE
  --   'S2'   <txns applied/sec>                        TRANSACTION APPLY RATE
  --   'S3'   <latency>                                                LATENCY
  --
  -- APPLY SUB-COMPONENT LEVEL:
  --   'S4'   <APS parallelism>
  --   'S5'   <idl%>                                                      IDLE
  --   'S6'   <flwctrl%>                                          FLOW CONTROL
  --   'S7'   <topevt%>                                               EVENT: %
  --
  -- EXTRACT:
  --   'S1'   <msgs sent/sec>                                        SEND RATE
  --   'S2'   <bytes sent/sec>                                 BYTES SEND RATE
  --   'S3'   <latency>                                                LATENCY
  -- 
  -- REPLICAT:
  --   'S1'   <msgs received/sec>                         MESSAGE RECEIVE RATE
  --   'S2'   <bytes received/sec>                          BYTES RECEIVE RATE

  ----------------------------------------------------------------------------=

  -- Record type for component statistic
  TYPE STAT_TYPE IS RECORD (
         advisor_run_id     number        default 0,
         advisor_run_time   date          default SYSDATE,
         path_id            number        default 0,
         position           number        default 0,
         component_id       number        default 0,
         component_name     varchar2(194),
         component_type     varchar2(30),
         sub_component_type varchar2(30),
         session_id         number,
         session_serial#    number,
         statistic_alias    varchar2(30)  default 'UNKNOWN',
         statistic_name     varchar2(64) default 'UNKNOWN',
         statistic_value    number        default 0,
         statistic_unit     varchar2(64));

  -- Stores a statistic name and value pair
  TYPE STAT_PAIR is Record(
   stat_name     varchar2(128) default 'UNKNOWN',
   stat_value    number        default -1);

  ----------------------------------------------------------------------------=
  -- get_acronym
  --   generates the component acronyms for the html report
  --  NOTE: keep in sync with component/subcomponent types listed in catsadv
  ----------------------------------------------------------------------------=
  function get_acronym(
    type in varchar2,
    subtype in varchar2 default ''
  ) 
  return varchar2 as
  begin
    if type = 'PROPAGATION SENDER' then
      return 'PS';
    elsif type = 'PROPAGATION RECEIVER' then
      return 'PR';
    elsif type = 'QUEUE' then
      return 'Q';
    elsif subtype = 'LOGMINER READER' then
      return 'LMR';
    elsif subtype = 'LOGMINER PREPARER' then
      return 'LMP';
    elsif subtype = 'LOGMINER BUILDER' then
      return 'LMB';
    elsif subtype = 'APPLY READER' then
      return 'APR';
    elsif subtype = 'APPLY COORDINATOR' then
      return 'APC';
    elsif subtype = 'APPLY SERVER' then
      return 'APS';
    elsif subtype = 'CAPTURE SESSION' then
      return 'CP';
    elsif subtype = 'PROPAGATION SENDER+RECEIVER' then
      return 'PS+PR';
    elsif type = 'CAPTURE' then
      return 'CAPTURE';
    elsif type = 'APPLY' then
      return 'APPLY';
    elsif type = 'EXTRACT' then
      return 'EXTRACT';
    elsif type = 'REPLICAT' then
      return 'REPLICAT';
    else
      return type || ' ' || subtype;
    end if;
  end get_acronym;

  ----------------------------------------------------------------------------=
  -- get_component_db
  --   provides component db name given a component id
  ----------------------------------------------------------------------------=
  function get_component_db (
    componentID in number
  )
  return varchar2 
  is
   compDB varchar2(128);
  begin
    begin
      select component_db into compDB from streams$_pa_component
        where component_id = componentID;
    exception when others then
      compDB := ' ';
    end;
    return compDB;
  end get_component_db;

  ----------------------------------------------------------------------------=
  -- check_report_directory
  --   provides component db name given a component id
  ----------------------------------------------------------------------------=
  function check_report_directory (
    dir in varchar2
  )
  return boolean
  is
   cnt number;
  begin
    cnt := 0;
    begin
      select count(*) into cnt from all_directories 
        where directory_name = dir;
    exception when others then
      return FALSE;
    end;

    if cnt > 0 then
      return TRUE;
    else 
      return FALSE;
    end if;

  end check_report_directory;

   ----------------------------------------------------------------------------=
  -- get_statistic_by_position
  --   Retrieve the statistis for a path and run based on component position
  ----------------------------------------------------------------------------=
  function get_statistic_by_position (
    comp_stat_table in varchar2,
    alias in varchar2,
    path_id in number,
    run_id in number,
    position in number,
    indexName in varchar2
  )
  return STAT_PAIR
  is 
    type cur_type is ref cursor;
    stat_cur cur_type;
    stat STAT_PAIR;
    sel_cursor   NUMBER;
    sel_stmt varchar2(4000);
    numRows number;
  begin
    
    sel_stmt := 'select /*+ INDEX(' || comp_stat_table || ' ' || 
                              indexName || ') */ ' ||
                             'statistic_name, statistic_value from ' ||
                              comp_stat_table || 
                              ' where statistic_alias = :alias' ||
                              ' and advisor_run_id = :run_id ' ||
                              ' and path_id = :path_id ' ||
                              ' and position = :position ' ||
                              ' and session_id is null  ' || 
                              ' and session_serial# is null';

    sel_cursor := dbms_sql.open_cursor;
    dbms_sql.parse(sel_cursor, sel_stmt, dbms_sql.native);
    dbms_sql.bind_variable(sel_cursor, ':alias', alias);
    dbms_sql.bind_variable(sel_cursor, ':run_id', run_id);
    dbms_sql.bind_variable(sel_cursor, ':path_id', path_id);
    dbms_sql.bind_variable(sel_cursor, ':position', position);

    dbms_sql.define_column(sel_cursor, 1, stat.stat_name, 128);
    dbms_sql.define_column(sel_cursor, 2, stat.stat_value);

    numRows := dbms_sql.execute_and_fetch(sel_cursor);
    if numRows > 0 then
      dbms_sql.column_value(sel_cursor, 1, stat.stat_name);
      dbms_sql.column_value(sel_cursor, 2, stat.stat_value);
    else
      stat.stat_name :=  null;
      stat.stat_value := -1;
    end if;
    dbms_sql.close_cursor(sel_cursor);

    return stat;
  end get_statistic_by_position;

  ----------------------------------------------------------------------------=
  -- get_statistic_by_component
  --   Retrieve statistics of a path and run based on the component type
  ----------------------------------------------------------------------------=

  function get_statistic_by_component(
    comp_stat_table in varchar2,
    alias in varchar2,
    path_id in number,
    run_id in number,
    comp_type in varchar2,
    comp_stype in varchar2
  )
  return STAT_PAIR
  is 
    type cur_type is ref cursor;
    stat_cur cur_type;
    stat STAT_PAIR;
    comp_acronym varchar2(20);
    sel_cursor   NUMBER;
    sel_stmt varchar2(4000);
    numRows number;
  begin
    
    comp_acronym := get_acronym(comp_type,comp_stype);
    sel_stmt := 'select statistic_name, statistic_value from ' ||
                              comp_stat_table || 
                              ' where statistic_alias = :alias ' ||
                              ' and advisor_run_id = :run_id ' ||
                              ' and path_id = :path_id ' ||   
                              ' and component_type = :comp_type' ||
                              case when comp_acronym = 'PS' 
                                        or comp_acronym ='PR'
                                        or comp_stype is null
                                then ' and sub_component_type is null'
                                else 
                                     ' and sub_component_type = ''' ||
                                     comp_stype || ''''
                              end ||
                              ' and session_id is null  ' || 
                              ' and session_serial# is null';

    sel_cursor := dbms_sql.open_cursor;
    dbms_sql.parse(sel_cursor, sel_stmt, dbms_sql.native);
    dbms_sql.bind_variable(sel_cursor, ':alias', alias);
    dbms_sql.bind_variable(sel_cursor, ':run_id', run_id);
    dbms_sql.bind_variable(sel_cursor, ':path_id', path_id);
    dbms_sql.bind_variable(sel_cursor, ':comp_type', comp_type);

    dbms_sql.define_column(sel_cursor, 1, stat.stat_name, 128);
    dbms_sql.define_column(sel_cursor, 2, stat.stat_value);

    numRows := dbms_sql.execute_and_fetch(sel_cursor);
    if numRows > 0 then
      dbms_sql.column_value(sel_cursor, 1, stat.stat_name);
      dbms_sql.column_value(sel_cursor, 2, stat.stat_value);
    else
      stat.stat_name :=  null;
      stat.stat_value := -1;
    end if;
    dbms_sql.close_cursor(sel_cursor);

    return stat;
  end get_statistic_by_component;


  ----------------------------------------------------------------------------=
  -- get_avg_statistic_by_component
  --   Retrieves the avg value of statistic across runs for a given path 
  --   and component type
  ----------------------------------------------------------------------------=
  function get_avg_statistic_by_component(
    comp_stat_table in varchar2,
    alias in varchar2,
    path_id in number,
    bgn_run_id in number,
    end_run_id in number,
    comp_type in varchar2,
    comp_stype in varchar2
  )
  return number
  is 
    type cur_type is ref cursor;
    stat_cur cur_type;
    statistic number;
    comp_acronym varchar2(20);
    sel_cursor   NUMBER;
    sel_stmt varchar2(4000);
    numRows number;
  begin
    
    comp_acronym := get_acronym(comp_type,comp_stype);
    sel_stmt := 'select avg(statistic_value) from ' ||
                              comp_stat_table || 
                              ' where statistic_alias = :alias' ||
                              ' and path_id = :path_id ' ||   
                              ' and advisor_run_id <= :end_run_id '||
                              ' and advisor_run_id >= :bgn_run_id ' ||
                              ' and component_type = :comp_type ' ||
                              case when comp_acronym = 'PS' 
                                        or comp_acronym ='PR'
                                        or comp_stype is null
                                then ' and sub_component_type is null'
                                else ' and sub_component_type = ''' || 
                                     comp_stype || ''''
                              end ||
                              ' and session_id is null  ' || 
                              ' and session_serial# is null';

    sel_cursor := dbms_sql.open_cursor;
    dbms_sql.parse(sel_cursor, sel_stmt, dbms_sql.native);
    dbms_sql.bind_variable(sel_cursor, ':alias', alias);
    dbms_sql.bind_variable(sel_cursor, ':end_run_id', end_run_id);
    dbms_sql.bind_variable(sel_cursor, ':path_id', path_id);
    dbms_sql.bind_variable(sel_cursor, ':comp_type', comp_type);
    dbms_sql.bind_variable(sel_cursor, ':bgn_run_id', bgn_run_id);

    dbms_sql.define_column(sel_cursor, 1, statistic);

    numRows := dbms_sql.execute_and_fetch(sel_cursor);
    if numRows > 0 then
      dbms_sql.column_value(sel_cursor, 1, statistic);
    else
      statistic := -1;
    end if;
    dbms_sql.close_cursor(sel_cursor);

    return statistic;
  end get_avg_statistic_by_component;

  ----------------------------------------------------------------------------=
  -- GET_RUN_TIME
  -- gets the run time for a run
  ----------------------------------------------------------------------------=
  function GET_RUN_TIME (
  comp_stat_table in varchar2,
  indexName in varchar2,
  run_id in number,
  path_id in number
  )
  return varchar2 as
    runTime varchar2(50);
    sel_cursor   NUMBER;
    sel_stmt varchar2(4000);
    numRows number;
  begin
    sel_stmt := 'select /* INDEX(' || comp_stat_table || ' ' ||
                indexName || ') */ ' ||
                'distinct ' || 
                'to_char(advisor_run_time,''DD-MON-YYYY HH24:MI:SS'') ' ||
                ' from ' || comp_stat_table || 
                ' where advisor_run_id = :run_id ' ||
                ' and path_id = :path_id ';
    sel_cursor := dbms_sql.open_cursor;
    dbms_sql.parse(sel_cursor, sel_stmt, dbms_sql.native);
    dbms_sql.bind_variable(sel_cursor, ':path_id', path_id);
    dbms_sql.bind_variable(sel_cursor, ':run_id', run_id);

    dbms_sql.define_column(sel_cursor, 1, runTime, 50);

    numRows := dbms_sql.execute_and_fetch(sel_cursor);
    if numRows > 0 then
      dbms_sql.column_value(sel_cursor, 1, runTime);
    else
      runTime := '';
    end if;
    dbms_sql.close_cursor(sel_cursor);
    return runTime;
  end GET_RUN_TIME;

  ----------------------------------------------------------------------------=
  -- GET_BOTTLENECK_HTML
  --   Get bottleneck information in the following form:
  --      advisor_run_reason
  --   OR
  --      component_acronym topevent% "topevent"
  ----------------------------------------------------------------------------=
  function GET_BOTTLENECK_HTML(run_id in number, path_id in number)
  return varchar2 as
    val  varchar2(4000) := null; 
    val2 varchar2(4000) := null;
    bott DBA_STREAMS_TP_COMPONENT_STAT%ROWTYPE;
  begin
    begin
      for bott in (
        select distinct S.*
        from dba_streams_tp_path_bottleneck B,
           ( select distinct *
             from dba_streams_tp_component_stat
             where statistic_name LIKE 'EVENT: %' and
                   session_id is not null and
                   session_serial# is not null and
                   advisor_run_id = GET_BOTTLENECK_HTML.run_id ) S
        where B.path_id = GET_BOTTLENECK_HTML.path_id and
              B.advisor_run_id = GET_BOTTLENECK_HTML.run_id and
              B.top_session_id is not null and
              B.top_session_serial# is not null and
              B.bottleneck_identified = 'YES' and
              B.advisor_run_id = S.advisor_run_id (+) and
              B.component_id = S.component_id (+) and
              B.top_session_id = S.session_id (+) and
              B.top_session_serial# = S.session_serial# (+)
        order by S.statistic_value desc)
      loop
        val := ' ' || get_acronym(bott.component_type, bott.sub_component_type) 
               ||' '|| to_char(bott.statistic_value, 'FM999D9') || '% "' ||
               regexp_replace(bott.statistic_name, '^EVENT: ', NULL) || '"';
        exit when val is not null;
      end loop;

      if val is null then
        -- post process for XStream Bottleneck in case 'EXTERNAL' is 
        -- the bottleneck. In this case, bottleneck_identified is set to 'YES'
        -- but component id/session_id/session_serial will all be NULL and will
        -- not be covered by previous step. 
        select '"EXTERNAL"' into val2
        from dba_streams_tp_path_bottleneck
        where advisor_run_id = GET_BOTTLENECK_HTML.run_id AND
              path_id = GET_BOTTLENECK_HTML.path_id AND
              action_name = 'EXTERNAL' AND
              bottleneck_identified = 'YES';
        
        if val2 is not null then
          val := val2;
        else
          select distinct ' "' || advisor_run_reason || '"' into val
          from dba_streams_tp_path_bottleneck B
          where B.path_id = GET_BOTTLENECK_HTML.path_id and
                B.advisor_run_id = GET_BOTTLENECK_HTML.run_id;
        end if;
      end if;

    exception when others then
       val := '' || 'NO BOTTLENECK IDENTIFIED';
    end;

    return val;
  end GET_BOTTLENECK_HTML;

  ----------------------------------------------------------------------------=
  -- get_bottleneck_percent
  --   returns the % of time a component was the bottleneck within some
  --   run id range
  ----------------------------------------------------------------------------=
  function get_bottleneck_percent(
    comp_acronym in varchar2,
    path_id number,
    bgn_run_id number,
    end_run_id number
  )
  return number
  is
    type cur_type is ref cursor;
    bottleneck_cur cur_type;
    bcount number;
    run_id number;
    bottleneckInfo varchar2(4000);
  begin
    
    bcount  := 0.0;
    for run_id in bgn_run_id .. end_run_id loop
        -- get the bottleneck info we wrote earlier
        open bottleneck_cur for 'select B.spare3 from ' || 
                                ' streams$_pa_path_bottleneck B' ||
                                ' where  B.advisor_run_id =' ||  run_id || 
                                ' and  B.path_id = ' || path_id;
        loop
          fetch bottleneck_cur into bottleneckInfo;
          exit when bottleneck_cur%notfound;

          if bottleneckInfo like '%'|| comp_acronym ||'%' then
            bcount := bcount + 1;
          end if;
          
        end loop;
        close bottleneck_cur;
    end loop;

    return (bcount / (end_run_id - bgn_run_id + 1)) * 100.0;
  end get_bottleneck_percent;


  ----------------------------------------------------------------------------=
  -- JOIN_VALUE
  --   Join values into a list.
  ----------------------------------------------------------------------------=
  function JOIN_VALUE(
    p_cur in sys_refcursor,
    p_del in varchar2 default ' ')
  return varchar2
  is
    l_value   varchar2(32767);
    l_result  varchar2(32767);
  begin
    begin
      loop
        fetch p_cur into l_value;
        exit when p_cur%notfound;
  
        if l_result is not null then
          l_result := l_result || p_del;
        end if;
  
        l_result := l_result || l_value;
      end loop;
      close p_cur;
    exception when others then
     if p_cur%isopen then
       close p_cur;
     end if;
     raise;
    end;

    return l_result;
  end JOIN_VALUE;

  ----------------------------------------------------------------------------=
  -- GET_COMP_TYPEE
  ----------------------------------------------------------------------------=
  function GET_COMP_TYPE(component_type in varchar2)
  return varchar2 is
    val varchar2(10);
  begin
    val := 
      case when component_type = 'CAPTURE'              then '|<C>'
           when component_type = 'APPLY'                then '|<A>'
           when component_type = 'QUEUE'                then '|<Q>'
           when component_type = 'PROPAGATION SENDER'   then '|<PS>'
           when component_type = 'PROPAGATION RECEIVER' then '|<PR>'
           when component_type = 'EXTRACT'              then '|<E>'
           when component_type = 'REPLICAT'             then '|<R>'
           else component_type
      end;

    return val;
  end GET_COMP_TYPE;

  ----------------------------------------------------------------------------=
  -- GET_COMP_NAME
  ----------------------------------------------------------------------------=
  function GET_COMP_NAME(component_type in varchar2,
                         component_name in varchar2)
  return varchar2 is
    val varchar2(194);
  begin
    -- PROPAGATION SENDER:
    --    "src_schema"."src_queue"=>"dst_schema"."dst_queue"@database
    -- changed to:
    --    =>database
    --
    -- PROPAGATION RECEIVER:
    --    "src_schema"."src_queue"@database=>"dst_schema"."dst_queue"
    -- changed to:
    --    database=>
    val := 
      case when component_type = 'PROPAGATION SENDER' then
             regexp_replace(
                regexp_replace(component_name, '^.*=>', '=>'), '^=>.*@', '=>')
           when component_type = 'PROPAGATION RECEIVER' then
             regexp_replace(
                regexp_replace(component_name, '=>.*$', '=>'), '^.*@', null)
           else component_name
      end;

    return val;
  end GET_COMP_NAME;

  ----------------------------------------------------------------------------=
  -- GET_SUB_COMP_TYPE
  ----------------------------------------------------------------------------=
  function GET_SUB_COMP_TYPE(sub_component_type in varchar2)
  return varchar2 is
    val varchar2(10);
  begin
    val :=
      case when sub_component_type = 'LOGMINER READER'        then 'LMR'
           when sub_component_type = 'LOGMINER PREPARER'      then 'LMP'
           when sub_component_type = 'LOGMINER BUILDER'       then 'LMB'
           when sub_component_type = 'CAPTURE SESSION'        then 'CAP'
           when sub_component_type = 'PROPAGATION SENDER+RECEIVER'
                                                              then 'PS+PR'
           when sub_component_type = 'APPLY READER'           then 'APR'
           when sub_component_type = 'APPLY COORDINATOR'      then 'APC'
           when sub_component_type = 'APPLY SERVER'           then 'APS'
           when sub_component_type = 'CAPTURE SESSION + PS'   then 'CAP+PS'
           else sub_component_type
      end;

    return val;
  end GET_SUB_COMP_TYPE;

  ----------------------------------------------------------------------------=
  -- JOIN_STAT
  ----------------------------------------------------------------------------=
  function JOIN_STAT(
    prev                in stat_type,    -- previously joined stat
    curr                in stat_type,    -- current stat to be joined
    top_event_threshold in number default 15,
    is_session_level    in boolean default FALSE)
  return varchar2
  is
    sval varchar2(800);
  begin
    -- Format statistic value
    if (curr.statistic_alias = 'S1' OR
        curr.statistic_alias = 'S2' ) then
      if (curr.statistic_name = 'BANDWIDTH') then
        -- Use scientific representation
        sval := to_char(curr.statistic_value, 'FM9.99EEEE');
      else
        -- Use integer for throughput (S1 and S2)
        if (curr.statistic_value > 10) then
          sval := to_char(curr.statistic_value, 'FM99999999999');
        elsif (curr.statistic_value > 0.01) then
          sval := to_char(curr.statistic_value, 'FM99999999999.99');
        else
          sval := '0.01';
        end if;
      end if;
    elsif (curr.statistic_alias = 'S3') then
      -- S3 is latency (accuracy at one tenth second)
      sval := to_char(curr.statistic_value, 'FM999999999D9');
    elsif (curr.statistic_alias = 'S5' OR
           curr.statistic_alias = 'S6' OR
           curr.statistic_alias = 'S7' ) then
      -- S5 S6 S7 are percentage
      sval := to_char(curr.statistic_value, 'FM99999999D9');
    elsif (curr.statistic_name = 'PARALLELISM') then
      -- Use integer for 'PARALLELISM'
      sval := to_char(curr.statistic_value, 'FM99999999');
    else
      sval := to_char(curr.statistic_value, 'FM999999999D9');
    end if;
  
    if sval is not null then
      sval := regexp_replace(sval, '^\.', '0.');
      sval := regexp_replace(sval, '\.$', null);
    end if;

    -- Add decoration to statistic value
    if (curr.statistic_alias = 'S5' OR
        curr.statistic_alias = 'S6' OR
        curr.statistic_alias = 'S7' ) then
      sval := sval || '%';
    end if;

    -- Show PARALLELISM for LMP and APC 
    if (curr.statistic_name = 'PARALLELISM') then
      sval := '(' || sval || ')';
    end if;

    -- Use prev to determine how to join current stat.
    -- Show TOP EVENT only if it meets the threshold
    if (curr.statistic_alias = 'S7') then
      if (prev.statistic_alias = 'S7' AND
          prev.statistic_value = curr.statistic_value) then
         -- Show only one TOP EVENT
         -- when multiple TOP EVENTs have the same statistic_value
         sval := null;
      else
        if (to_number(curr.statistic_value) > top_event_threshold) then
           sval := sval || ' "' || curr.statistic_name || '"';
        else
           sval := sval || ' ""';
        end if;
      end if;
    end if;

    -- Session-Level STATISTICS
    if is_session_level = TRUE then
      -- Add SESSION_ID and SESSION_SERIAL#
      if (curr.statistic_alias = 'S5') then
        sval := curr.session_id ||' '||
                curr.session_serial# ||' '||
                sval;
      end if;

      -- Add SUB_COMPONENT_TYPE
      if (curr.statistic_alias = 'S5' AND
          curr.component_type in ('CAPTURE', 'APPLY')) then
        sval := get_sub_comp_type(curr.sub_component_type) ||' '||
                sval;
      end if;

    -- Component-Level and Sub-Component-Level STATISTICS
    else
      -- Add SUB_COMPONENT_TYPE
      if (curr.sub_component_type is not null AND
          (prev.sub_component_type is null OR
           prev.sub_component_type != curr.sub_component_type)) then
        sval := get_sub_comp_type(curr.sub_component_type) ||' '|| sval;
      end if;
    end if;

    -- Add COMPONENT_TYPE and COMPONENT_NAME
    if (curr.component_id != prev.component_id) then
      sval := get_comp_type(curr.component_type) ||' '||
              get_comp_name(curr.component_type, curr.component_name) ||' '||
              sval;
    end if;

    return sval;
  end JOIN_STAT;

  ----------------------------------------------------------------------------=
  -- INIT_SPADV_COMP_STAT
  --   Create a table (default STREAMS$_ADVISOR_COMP_STAT) for comp statistics.
  ----------------------------------------------------------------------------=
  function INIT_SPADV_COMP_STAT(
    output_table in varchar2 default 'STREAMS$_ADVISOR_COMP_STAT'
  ) return varchar2 as
    stmt     varchar2(4000) := null;
    indStmt  varchar2(4000) := null;
    tbl_name dbms_id        := null;
    tbl_desc varchar2(4000) := null;
    tbl_sign varchar2(1000) :=
      '1 ADVISOR_RUN_ID NUMBER 2 ADVISOR_RUN_TIME DATE ' ||
      '3 PATH_ID NUMBER 4 POSITION NUMBER ' ||
      '5 COMPONENT_ID NUMBER 6 COMPONENT_NAME VARCHAR2 ' ||
      '7 COMPONENT_TYPE VARCHAR2 8 SUB_COMPONENT_TYPE VARCHAR2 ' ||
      '9 SESSION_ID NUMBER 10 SESSION_SERIAL# NUMBER '||
      '11 STATISTIC_ALIAS VARCHAR2 12 STATISTIC_NAME VARCHAR2 ' ||
      '13 STATISTIC_VALUE NUMBER 14 STATISTIC_UNIT VARCHAR2';

    cur sys_refcursor;
    tbl_exception exception;
    PRAGMA EXCEPTION_INIT(tbl_exception, -955);
  begin
    if output_table is not null then
      tbl_name := dbms_assert.qualified_sql_name(output_table);
    else
      tbl_name := 'STREAMS$_ADVISOR_COMP_STAT';
    end if;
    tbl_name := upper(tbl_name);
  
    stmt := 'create table ' || tbl_name || '(' ||
            ' advisor_run_id     number, '||
            ' advisor_run_time   date, '||
            ' path_id            number, '||
            ' position           number, '||
            ' component_id       number, '||
            ' component_name     varchar2(194), '||
            ' component_type     varchar2(30), '||
            ' sub_component_type varchar2(30), '||
            ' session_id         number, '||
            ' session_serial#    number, '||
            ' statistic_alias    varchar2(30), '||
            ' statistic_name     varchar2(64), '||
            ' statistic_value    number, '||
            ' statistic_unit     varchar2(64))';

    -- create a unique to speed queries up for spadv html report
    indStmt := 'create index '|| tbl_name ||'_pk on ' || tbl_name ||
                ' (advisor_run_id, path_id, position, statistic_alias)';

    begin
      select table_name into tbl_name from user_tables
      where table_name = tbl_name;
    exception when NO_DATA_FOUND then
      execute immediate stmt;
      execute immediate indStmt;
      dbms_output.put_line('create table ' || tbl_name);
    end;
  
    begin
      open cur for
        select column_id || ' ' || column_name || ' ' || data_type
        from user_tab_columns
        where table_name = tbl_name
        order by column_id;

      tbl_desc := join_value(cur, ' ');
    exception when others then
      tbl_desc := 'UNKNOWN';
    end;

    -- Throw exception if the two tables do not have the same shape.
    if tbl_desc != tbl_sign then
      raise tbl_exception;
    end if;
  
    return tbl_name;
  end INIT_SPADV_COMP_STAT;
  
  ----------------------------------------------------------------------------=
  -- CHECK_SPADV_COMP_STAT
  --   Check the table (default STREAMS$_ADVISOR_COMP_STAT)
  ----------------------------------------------------------------------------=
  function CHECK_SPADV_COMP_STAT(
    output_table in varchar2 default 'STREAMS$_ADVISOR_COMP_STAT'
  ) 
  return varchar2 as
    tbl_name dbms_id        := null;
    tbl_desc varchar2(4000) := null;
    tbl_sign varchar2(1000) :=
      '1 ADVISOR_RUN_ID NUMBER 2 ADVISOR_RUN_TIME DATE ' ||
      '3 PATH_ID NUMBER 4 POSITION NUMBER ' ||
      '5 COMPONENT_ID NUMBER 6 COMPONENT_NAME VARCHAR2 ' ||
      '7 COMPONENT_TYPE VARCHAR2 8 SUB_COMPONENT_TYPE VARCHAR2 ' ||
      '9 SESSION_ID NUMBER 10 SESSION_SERIAL# NUMBER '||
      '11 STATISTIC_ALIAS VARCHAR2 12 STATISTIC_NAME VARCHAR2 ' ||
      '13 STATISTIC_VALUE NUMBER 14 STATISTIC_UNIT VARCHAR2';
    cur sys_refcursor;
  begin
    if output_table is not null then
      tbl_name := dbms_assert.qualified_sql_name(output_table);
    else
      tbl_name := 'STREAMS$_ADVISOR_COMP_STAT';
    end if;

    tbl_name := upper(tbl_name);
  
    begin
      select table_name into tbl_name from user_tables
      where table_name = tbl_name;
    exception when NO_DATA_FOUND then
      raise_application_error(-20100,
        'Non-existing table ''' || output_table || '''');
    end;
  
    begin
      open cur for
        select column_id || ' ' || column_name || ' ' || data_type
        from user_tab_columns
        where table_name = tbl_name
        order by column_id;

      tbl_desc := join_value(cur, ' ');
    exception when others then
      tbl_desc := 'UNKNOWN';
    end;
  
    if tbl_desc != tbl_sign then
      raise_application_error(-20100,
        'Invalid table ''' || output_table || '''');
    end if;
  
    return tbl_name;
  end CHECK_SPADV_COMP_STAT;

  ----------------------------------------------------------------------------=
  -- INIT_SPADV_PATH_STAT
  --   Create a table (default STREAMS$_ADVISOR_PATH_STAT) for path statistics.
  ----------------------------------------------------------------------------=
  function INIT_SPADV_PATH_STAT(
    output_table in varchar2 default 'STREAMS$_ADVISOR_PATH_STAT'
  ) return varchar2 as
    stmt     varchar2(4000)  := null;
    tbl_name dbms_id         := null;
    tbl_desc varchar2(4000)  := null;
    tbl_sign varchar2(1000)  :=
      '1 PATH_ID NUMBER 2 ADVISOR_RUN_ID NUMBER 3 ADVISOR_RUN_TIME DATE '
    ||'4 SETTING VARCHAR2 5 STATISTICS VARCHAR2 6 SESSION_STATISTICS VARCHAR2 '
    ||'7 OPTIMIZATION NUMBER';

    cur sys_refcursor;
    tbl_exception exception;
    PRAGMA EXCEPTION_INIT(tbl_exception, -955); 
  begin
    if output_table is not null then
      tbl_name := dbms_assert.qualified_sql_name(output_table);
    else
      tbl_name := 'STREAMS$_ADVISOR_PATH_STAT';
    end if;
    tbl_name := upper(tbl_name);
  
    stmt := 'create table ' || tbl_name || '(' ||
            ' path_id            number, ' ||
            ' advisor_run_id     number, ' ||
            ' advisor_run_time   date,   ' ||
            ' setting            varchar2(2000), ' ||
            ' statistics         varchar2(4000), ' ||
            ' session_statistics varchar2(4000), ' ||
            ' optimization       number)';
  
    begin
      select table_name into tbl_name from user_tables
      where table_name = tbl_name;
    exception when NO_DATA_FOUND then
      execute immediate stmt;
      dbms_output.put_line('create table ' || tbl_name);
    end;
  
    begin
      open cur for
        select column_id || ' ' || column_name || ' ' || data_type
        from user_tab_columns
        where table_name = tbl_name
        order by column_id;

      tbl_desc := join_value(cur, ' ');
    exception when others then
      tbl_desc := 'UNKNOWN';
    end;
  
    -- Throw exception if the two tables do not have the same shape.
    if tbl_desc != tbl_sign then
      raise tbl_exception;
    end if;
  
    return tbl_name;
  end INIT_SPADV_PATH_STAT;
  
  ----------------------------------------------------------------------------=
  -- CHECK_SPADV_PATH_STAT
  --   Check the table (default STREAMS$_ADVISOR_PATH_STAT) for path statistics
  ----------------------------------------------------------------------------=
  function CHECK_SPADV_PATH_STAT(
    output_table in varchar2 default 'STREAMS$_ADVISOR_PATH_STAT'
  ) return varchar2 as
    tbl_name dbms_id         := null;
    tbl_desc varchar2(4000)  := null;
    tbl_sign varchar2(1000)  :=
      '1 PATH_ID NUMBER 2 ADVISOR_RUN_ID NUMBER 3 ADVISOR_RUN_TIME DATE '
    ||'4 SETTING VARCHAR2 5 STATISTICS VARCHAR2 6 SESSION_STATISTICS VARCHAR2 '
    ||'7 OPTIMIZATION NUMBER';
    cur sys_refcursor;
  begin
    if output_table is not null then
      tbl_name := dbms_assert.qualified_sql_name(output_table);
    else
      tbl_name := 'STREAMS$_ADVISOR_PATH_STAT';
    end if;
    tbl_name := upper(tbl_name);
  
    begin
      select table_name into tbl_name from user_tables
      where table_name = tbl_name;
    exception when NO_DATA_FOUND then
      raise_application_error(-20100,
        'Non-existing table ''' || output_table || '''');
    end;
  
    begin
      open cur for
        select column_id || ' ' || column_name || ' ' || data_type
        from user_tab_columns
        where table_name = tbl_name
        order by column_id;

      tbl_desc := join_value(cur, ' ');
    exception when others then
      tbl_desc := 'UNKNOWN';
    end;
  
    if tbl_desc != tbl_sign then
      raise_application_error(-20100,
        'Invalid table ''' || output_table || '''');
    end if;
  
    return tbl_name;
  end CHECK_SPADV_PATH_STAT;

  ----------------------------------------------------------------------------=
  -- Init tables required by monitoring API.
  --
  ----------------------------------------------------------------------------=
  function INIT_TBL(tbl_name in varchar2,
                    tbl_sign in varchar2,
                    stmt     in varchar2)
  return varchar2 as
    t_name dbms_id        := null;
    t_sign varchar2(4000) := null;
    cur sys_refcursor;
    tbl_exception exception;
    PRAGMA EXCEPTION_INIT(tbl_exception, -955); 
  begin
    begin
      select table_name into t_name from user_tables
      where table_name = tbl_name;
    exception when NO_DATA_FOUND then
      execute immediate stmt;
      dbms_output.put_line('create table ' || tbl_name);
    end;
  
    begin
      open cur for
        select column_id || ' ' || column_name || ' ' || data_type
        from user_tab_columns
        where table_name = tbl_name
        order by column_id;

      t_sign := join_value(cur, ' ');
    exception when others then
      t_sign := 'UNKNOWN';
    end;
  
    -- Throw exception if the two tables do not have the same shape.
    if t_sign != tbl_sign then
      raise tbl_exception;
    end if;
  
    return tbl_name;
  end INIT_TBL;

  function INIT_TBL_PA_MONITORING
  return varchar2 as
    stmt     varchar2(4000)  := null;
    tbl_sign varchar2(1000)  := null;
    tbl_name varchar2(30)    := 'STREAMS$_PA_MONITORING';
  begin
    stmt :=
       'create table ' || tbl_name || '(' ||
       ' job_name           varchar2(128) not null, ' ||
       ' client_name        varchar2(128) default null, ' ||
       ' query_user_name    varchar2(128) default null, ' ||
       ' show_stats_table   varchar2(128) default ' ||
                                         '''STREAMS$_PA_SHOW_PATH_STAT'', ' ||
       ' started_time       timestamp default null, ' ||
       ' stopped_time       timestamp default null, ' ||
       ' altered_time       timestamp default null, ' ||
       ' state              varchar2(30) default null)';

    tbl_sign :=
       '1 JOB_NAME VARCHAR2 '||
       '2 CLIENT_NAME VARCHAR2 '||
       '3 QUERY_USER_NAME VARCHAR2 '||
       '4 SHOW_STATS_TABLE VARCHAR2 '||
       '5 STARTED_TIME TIMESTAMP(6) '||
       '6 STOPPED_TIME TIMESTAMP(6) '||
       '7 ALTERED_TIME TIMESTAMP(6) '||
       '8 STATE VARCHAR2';

    return INIT_TBL(tbl_name, tbl_sign, stmt);
  end INIT_TBL_PA_MONITORING;

  function INIT_TBL_PA_DATABASE
  return varchar2 as
    stmt     varchar2(4000)  := null;
    tbl_sign varchar2(1000)  := null;
    tbl_name varchar2(30)    := 'STREAMS$_PA_DATABASE';
  begin
    stmt :=
      'create table ' || tbl_name || '(' ||
      ' global_name        varchar2(128) not null, ' ||
      ' last_queried       date default null, ' ||
      ' error_number       number default null, ' ||
      ' error_message      varchar2(4000) default null)';

    tbl_sign :=
      '1 GLOBAL_NAME VARCHAR2 '||
      '2 LAST_QUERIED DATE '||
      '3 ERROR_NUMBER NUMBER '||
      '4 ERROR_MESSAGE VARCHAR2';

    return INIT_TBL(tbl_name, tbl_sign, stmt);
  end INIT_TBL_PA_DATABASE;

  function INIT_TBL_PA_DATABASE_PROP
  return varchar2 as
    stmt     varchar2(4000)  := null;
    tbl_sign varchar2(1000)  := null;
    tbl_name varchar2(30)    := 'STREAMS$_PA_DATABASE_PROP';
  begin
    stmt :=
      'create table ' || tbl_name || '(' ||
      ' global_name        varchar2(128) not null, ' ||
      ' prop_name          varchar2(128), ' ||
      ' prop_value         varchar2(128))';

    tbl_sign :=
      '1 GLOBAL_NAME VARCHAR2 '||
      '2 PROP_NAME VARCHAR2 '||
      '3 PROP_VALUE VARCHAR2';

    return INIT_TBL(tbl_name, tbl_sign, stmt);
  end INIT_TBL_PA_DATABASE_PROP;

  function INIT_TBL_PA_COMPONENT
  return varchar2 as
    stmt     varchar2(4000)  := null;
    tbl_sign varchar2(1000)  := null;
    tbl_name varchar2(30)    := 'STREAMS$_PA_COMPONENT';
  begin
    stmt :=
      'create table ' || tbl_name || '(' ||
      ' component_id       number not null, ' ||
      ' component_name     varchar2(194), ' ||
      ' component_db       varchar2(128), ' ||
      ' component_type     varchar2(20), ' ||
      ' component_changed_time date, ' ||
      ' spare1             number, ' ||
      ' spare2             number, ' ||
      ' spare3             varchar2(4000), ' ||
      ' spare4             date)';

    tbl_sign :=
       '1 COMPONENT_ID NUMBER '||
       '2 COMPONENT_NAME VARCHAR2 '||
       '3 COMPONENT_DB VARCHAR2 '||
       '4 COMPONENT_TYPE VARCHAR2 '||
       '5 COMPONENT_CHANGED_TIME DATE '||
       '6 SPARE1 NUMBER '||
       '7 SPARE2 NUMBER '||
       '8 SPARE3 VARCHAR2 '||
       '9 SPARE4 DATE';

    return INIT_TBL(tbl_name, tbl_sign, stmt);
  end INIT_TBL_PA_COMPONENT;

  function INIT_TBL_PA_COMPONENT_LINK
  return varchar2 as
    stmt     varchar2(4000)  := null;
    tbl_name varchar2(30)    := 'STREAMS$_PA_COMPONENT_LINK';
    tbl_sign varchar2(1000)  := null;
  begin
    stmt :=
      'create table ' || tbl_name || '(' ||
      ' path_id             number not null, ' ||
      ' path_key            varchar2(4000), ' ||
      ' source_component_id number not null, ' ||
      ' destination_component_id number not null, ' ||
      ' position            number, ' ||
      ' spare1              number, ' ||
      ' spare2              number, ' ||
      ' spare3              varchar2(4000), ' ||
      ' spare4              date)';

    tbl_sign :=
       '1 PATH_ID NUMBER '||
       '2 PATH_KEY VARCHAR2 '||
       '3 SOURCE_COMPONENT_ID NUMBER '||
       '4 DESTINATION_COMPONENT_ID NUMBER '||
       '5 POSITION NUMBER '||
       '6 SPARE1 NUMBER '||
       '7 SPARE2 NUMBER '||
       '8 SPARE3 VARCHAR2 '||
       '9 SPARE4 DATE';

    return INIT_TBL(tbl_name, tbl_sign, stmt);
  end INIT_TBL_PA_COMPONENT_LINK;

  function INIT_TBL_PA_COMPONENT_PROP
  return varchar2 as
    stmt     varchar2(4000)  := null;
    tbl_name varchar2(30)    := 'STREAMS$_PA_COMPONENT_PROP';
    tbl_sign varchar2(1000)  := null;
  begin
    stmt :=
      'create table ' || tbl_name || '(' ||
      ' component_id   number not null, ' ||
      ' prop_name      varchar2(128), ' ||
      ' prop_value     varchar2(4000), ' ||
      ' spare1         number, ' ||
      ' spare2         number, ' ||
      ' spare3         varchar2(4000), ' ||
      ' spare4         date)';

    tbl_sign :=
      '1 COMPONENT_ID NUMBER '||
      '2 PROP_NAME VARCHAR2 '||
      '3 PROP_VALUE VARCHAR2 '||
      '4 SPARE1 NUMBER '||
      '5 SPARE2 NUMBER '||
      '6 SPARE3 VARCHAR2 '||
      '7 SPARE4 DATE';

    return INIT_TBL(tbl_name, tbl_sign, stmt);
  end INIT_TBL_PA_COMPONENT_PROP;

  function INIT_TBL_PA_CONTROL
  return varchar2 as
    stmt     varchar2(4000)  := null;
    tbl_name varchar2(30)    := 'STREAMS$_PA_CONTROL';
    tbl_sign varchar2(1000)  := null;
  begin
    stmt :=
      'create table ' || tbl_name || '(' ||
      ' advisor_run_id     number, '||
      ' advisor_run_time   date, '||
      ' param_name         varchar2(128), '||
      ' param_value        varchar2(4000), '||
      ' param_unit         varchar2(128), '||
      ' spare1             number, '||
      ' spare2             number, '||
      ' spare3             varchar2(4000), '||
      ' spare4             date)';

    tbl_sign :=
      '1 ADVISOR_RUN_ID NUMBER '||
      '2 ADVISOR_RUN_TIME DATE '||
      '3 PARAM_NAME VARCHAR2 '||
      '4 PARAM_VALUE VARCHAR2 '||
      '5 PARAM_UNIT VARCHAR2 '||
      '6 SPARE1 NUMBER '||
      '7 SPARE2 NUMBER '||
      '8 SPARE3 VARCHAR2 '||
      '9 SPARE4 DATE';

    return INIT_TBL(tbl_name, tbl_sign, stmt);
  end INIT_TBL_PA_CONTROL;

  function INIT_TBL_PA_COMPONENT_STAT
  return varchar2 as
    stmt     varchar2(4000)  := null;
    tbl_name varchar2(30)    := 'STREAMS$_PA_COMPONENT_STAT';
    tbl_sign varchar2(1000)  := null;
  begin
    stmt :=
      'create table ' || tbl_name || '(' ||
      ' advisor_run_id   number, '||
      ' advisor_run_time date, '||
      ' component_id     number, '||
      ' statistic_time   date, '||
      ' statistic_name   varchar2(64), '||
      ' statistic_value  number, '||
      ' statistic_unit   varchar2(64), '||
      ' sub_component_type varchar2(64) default null, '||
      ' session_id       number default null, '||
      ' session_serial#  number default null, '||
      ' spare1           number, '||
      ' spare2           number, '||
      ' spare3           varchar2(4000), '||
      ' spare4           date)';

    tbl_sign :=
      '1 ADVISOR_RUN_ID NUMBER '||
      '2 ADVISOR_RUN_TIME DATE '||
      '3 COMPONENT_ID NUMBER '||
      '4 STATISTIC_TIME DATE '||
      '5 STATISTIC_NAME VARCHAR2 '||
      '6 STATISTIC_VALUE NUMBER '||
      '7 STATISTIC_UNIT VARCHAR2 '||
      '8 SUB_COMPONENT_TYPE VARCHAR2 '||
      '9 SESSION_ID NUMBER '||
      '10 SESSION_SERIAL# NUMBER '||
      '11 SPARE1 NUMBER '||
      '12 SPARE2 NUMBER '||
      '13 SPARE3 VARCHAR2 '||
      '14 SPARE4 DATE';

    return INIT_TBL(tbl_name, tbl_sign, stmt);
  end INIT_TBL_PA_COMPONENT_STAT;

  function INIT_TBL_PA_PATH_STAT
  return varchar2 as
    stmt     varchar2(4000)  := null;
    tbl_name varchar2(30)    := 'STREAMS$_PA_PATH_STAT';
    tbl_sign varchar2(1000)  := null;
  begin
    stmt :=
      'create table ' || tbl_name || '(' ||
      ' advisor_run_id   number, '||
      ' advisor_run_time date, '||
      ' path_id          number, '||
      ' path_key         varchar2(4000), '||
      ' statistic_time   date, '||
      ' statistic_name   varchar2(64), '||
      ' statistic_value  number, '||
      ' statistic_unit   varchar2(64), '||
      ' spare1           number, '||
      ' spare2           number, '||
      ' spare3           varchar2(4000), '||
      ' spare4           date)';

    tbl_sign :=
      '1 ADVISOR_RUN_ID NUMBER '||
      '2 ADVISOR_RUN_TIME DATE '||
      '3 PATH_ID NUMBER '||
      '4 PATH_KEY VARCHAR2 '||
      '5 STATISTIC_TIME DATE '||
      '6 STATISTIC_NAME VARCHAR2 '||
      '7 STATISTIC_VALUE NUMBER '||
      '8 STATISTIC_UNIT VARCHAR2 '||
      '9 SPARE1 NUMBER '||
      '10 SPARE2 NUMBER '||
      '11 SPARE3 VARCHAR2 '||
      '12 SPARE4 DATE';

    return INIT_TBL(tbl_name, tbl_sign, stmt);
  end INIT_TBL_PA_PATH_STAT;

  function INIT_TBL_PA_PATH_BOTTLENECK
  return varchar2 as
    stmt     varchar2(4000)  := null;
    tbl_name varchar2(30)    := 'STREAMS$_PA_PATH_BOTTLENECK';
    tbl_sign varchar2(1000)  := null;
  begin
    stmt :=
      'create table ' || tbl_name || '(' ||
      ' advisor_run_id      number, '||
      ' advisor_run_time    date, '||
      ' advisor_run_reason  varchar2(4000), '||
      ' path_id             number, '||
      ' path_key            varchar2(4000), '||
      ' component_id        number, '||
      ' top_session_id      number, '||
      ' top_session_serial# number, '||
      ' action_name         varchar2(130), '||
      ' bottleneck_identified varchar2(128), '||
      ' spare1              number, '||
      ' spare2              number, '||
      ' spare3              varchar2(4000), '||
      ' spare4              date)';

    tbl_sign :=
      '1 ADVISOR_RUN_ID NUMBER '||
      '2 ADVISOR_RUN_TIME DATE '||
      '3 ADVISOR_RUN_REASON VARCHAR2 '||
      '4 PATH_ID NUMBER '||
      '5 PATH_KEY VARCHAR2 '||
      '6 COMPONENT_ID NUMBER '||
      '7 TOP_SESSION_ID NUMBER '||
      '8 TOP_SESSION_SERIAL# NUMBER '||
      '9 ACTION_NAME VARCHAR2 '||
      '10 BOTTLENECK_IDENTIFIED VARCHAR2 '||
      '11 SPARE1 NUMBER '||
      '12 SPARE2 NUMBER '||
      '13 SPARE3 VARCHAR2 '||
      '14 SPARE4 DATE';

    return INIT_TBL(tbl_name, tbl_sign, stmt);
  end INIT_TBL_PA_PATH_BOTTLENECK;

  function INIT_TBL_PA_SHOW_COMP_STAT
  return varchar2 as
    tbl_name  dbms_id := 'STREAMS$_PA_SHOW_COMP_STAT';
  begin
    return INIT_SPADV_COMP_STAT(tbl_name);
  end INIT_TBL_PA_SHOW_COMP_STAT;

  function INIT_TBL_PA_SHOW_PATH_STAT
  return varchar2 as
    tbl_name  dbms_id := 'STREAMS$_PA_SHOW_PATH_STAT';
  begin
    return INIT_SPADV_PATH_STAT(tbl_name);
  end INIT_TBL_PA_SHOW_PATH_STAT;

  ----------------------------------------------------------------------------=
  -- AGGREGATE_TOP_EVENT
  --   Aggregate TOP EVENT statistics for streams subcomponents that include
  --   LOGMINER PREPARER and APPLY SERVER because of parallel sessions. Only
  --   the top three event of each parallel session will be considered.
  --
  --   Return the aggregated TOP EVENT PERCENTAGE over all session of a Streams
  --   sub-component and the TOP EVENT of the sub-component. For example if the
  --   APPLY SERVER has 3 parallel sessions, each having 3 top events:
  --     Session 79  TOP EVENT 1: 'CPU + Wait for CPU'  65
  --                 TOP EVENT 2: 'X'                   20
  --                 TOP EVENT 3: 'Y'                    5
  --
  --     Session 80  TOP EVENT 1: 'CPU + Wait for CPU'  45
  --                 TOP EVENT 2: 'X'                   35
  --                 TOP EVENT 3: 'Y'                   17
  --
  --     Session 81  TOP EVENT 1: 'X'                   40
  --                 TOP EVENT 2: 'CPU + Wait for CPU'  25
  --                 TOP EVENT 3: 'Z'                   25
  --
  --   The aggregated TOP EVENT percentage over the three sessions will be:
  --                 TOP EVENT 1: 'CPU + Wait for CPU'  65+45+25=135
  --                 TOP EVENT 2: 'X'                   20+35+40=95
  --                 TOP EVENT 3: 'Z'                   25
  --                 TOP EVENT 4: 'Y'                   5+17=22
  --
  --   The TOP EVENT of the sub-component is 'CPU + Wait for CPU'. So the
  --   returned value in varchar2 is as follows:
  --     135.0% "CPU + Wait for CPU"
  ----------------------------------------------------------------------------=
  function AGGREGATE_TOP_EVENT(
    run_id   in number,
    comp_id  in number,
    sub_type in varchar2,
    top_event_threshold in number default 15)
  return varchar2 as
    TYPE cur_type is ref cursor;
    stmt       varchar2(4000) := null;
    sval       varchar2(800)  := null;
    stat_cur   cur_type;
    stat_name  dba_streams_tp_component_stat.statistic_name%TYPE := null;
    stat_value dba_streams_tp_component_stat.statistic_value%TYPE := 0;
  begin
    stmt :=
      ' select statistic_name, statistic_value '||
      ' from ( select statistic_name, statistic_value '||
             ' from ( select distinct statistic_name, '||
                           ' SUM(statistic_value) as statistic_value '||
                      ' from dba_streams_tp_component_stat '||
                      ' where statistic_name like ''EVENT:%'' and '||
                            ' session_id is not null and  '||
                            ' session_serial# is not null and '||
                            ' component_id = ' || comp_id || ' and '||
                            ' advisor_run_id = ' || run_id || ' and '||
                            ' sub_component_type = ''' || sub_type || ''' '||
                      ' group by statistic_name ) '||
             ' order by statistic_value DESC, statistic_name ) '||
      ' where rownum = 1';

    begin
      open stat_cur for stmt;
      fetch stat_cur into stat_name, stat_value;
      close stat_cur;
    exception when others then
      if stat_cur%isopen then
        close stat_cur;
      end if;
      raise;
    end;

    -- Represent TOP EVENT in percentage
    sval := to_char(stat_value, 'FM9999D9');
    sval := regexp_replace(sval, '^\.', '0.');
    sval := regexp_replace(sval, '\.$', null);
    sval := sval || '% "' ||
            case when stat_value > top_event_threshold then
                      regexp_replace(stat_name, '^EVENT: ', NULL)
                 else null
            end  || '"';

    return sval;
  end AGGREGATE_TOP_EVENT;

  ----------------------------------------------------------------------------=
  -- PREPARE_CAP_PS
  --   Prepare statistics for 'CAP+PS' which is a capture and also a propagaton
  --   sender in CCA. Note that capture and propagaton sender share the same
  --   session_id and session_serial# in CCA mode.
  --
  -- PREPARE_CAP_PS is for converting the following
  --
  -- |<C> CAPTURE_USER1 2 0 0 0.E+00 LMR 97% 0% 3% "" LMP (1) 99.7% 0% 0.3% ""
  -- LMB 99% 0% 1% "" CAP 99.3% 0% 0.7% "" |<Q> "STRSEEDADM"."STRM_SEED_Q" 0 0
  -- 0 |<PS> =>DBS2.REGRESS.RDBMS.DEV.US.ORACLE.COM 0 0 2 99.3% 0% 0.7% ""
  -- |<PR> DBS1.REGRESS.RDBMS.DEV.US.ORACLE.COM=> 100% 0% 0% "" |<PR> ...
  -- |<C> CAPTURE_USER1 LMR 75 10 97% 0% 3% "" LMP 77 5 99.7% 0% 0.3% ""
  -- LMB 83 7 99% 0% 1% "" CAP 92 7 99.3% 0% 0.7% "" |<PS>
  -- =>DBS2.REGRESS.RDBMS.DEV.US.ORACLE.COM 92 7 99.3% 0% 0.7% "" |<PR> ...
  --
  -- TO
  --
  -- |<C> CAPTURE_USER1=>DBS2.REGRESS.RDBMS.DEV.US.ORACLE.COM 2 0 0 0.E+00
  -- LMR 97% 0% 3% "" LMP (1) 99.7% 0% 0.3% "" LMB 99% 0% 1% "" CAP+PS 0 0 2
  -- 99.3% 0% 0.7% "" |<PR> ...
  -- |<C> CAPTURE_USER1=>DBS2.REGRESS.RDBMS.DEV.US.ORACLE.COM
  -- LMR 75 10 97% 0% 3% "" LMP 77 5 99.7% 0% 0.3% "" LMB 83 7 99% 0% 1% ""
  -- CAP+PS 92 7 99.3% 0% 0.7% "" |<PR> ...
  --
  ----------------------------------------------------------------------------=
  procedure PREPARE_CAP_PS(
    run_id   in number,
    tbl_name in varchar2) as
    TYPE cur_type is ref cursor;
    s_cur    cur_type;
    s_rec    stat_type;
    stmt1    varchar2(4000) := null;
    stmt2    varchar2(4000) := null;
  begin

    -- Find 'CAPTURE SESSION' sharing the same path_id and
    -- the same session_id/serial# with 'PROPAGATON SENDER'
    stmt1 :=
     ' select A.advisor_run_id, '||
            ' A.advisor_run_time, '||
            ' A.path_id, '||
            ' A.position, '||
            ' A.component_id, '||
            ' (A.component_name || '||
            '  regexp_replace( '||
            '    regexp_replace(B.component_name, ''^.*=>'', ''=>''), '||
            '    ''^=>.*@'', ''=>'') ), '||
            ' A.component_type, '||
            ' A.sub_component_type, '||
            ' A.session_id, '||
            ' A.session_serial#, '||
            ' A.statistic_alias, '||
            ' A.statistic_name, '||
            ' A.statistic_value, '||
            ' A.statistic_unit '||
     ' from ' || tbl_name || ' A, ' || tbl_name || ' B '||
     ' where A.component_type = ''CAPTURE'' '||
       ' and A.sub_component_type = ''CAPTURE SESSION'' '||
       -- statistic IDLE is guaranteed by performance advisor
       ' and A.statistic_name = ''IDLE'' '||
       ' and B.component_type = ''PROPAGATION SENDER'' '||
       ' and B.sub_component_type is null '||
       -- statistic IDLE is guaranteed by performance advisor
       ' and B.statistic_name = ''IDLE'' '||
       ' and A.path_id = B.path_id '||
       ' and A.session_id = B.session_id '||
       ' and A.session_serial# = B.session_serial# '||
       ' and A.advisor_run_id = B.advisor_run_id '||
       ' and A.advisor_run_time = B.advisor_run_time '||
       ' and (A.advisor_run_id, A.advisor_run_time) in '||
           ' (select distinct advisor_run_id, advisor_run_time '||
           '  from dba_streams_tp_component_stat '||
           '  where advisor_run_id = '|| run_id ||')';

    begin
      open s_cur for stmt1;
      loop
        fetch s_cur into s_rec;
        exit when s_cur%notfound;

        -- Change 'PROPAGATION SENDER' to 'CAPTURE SESSION + PS'
        stmt2 :=
         ' update ' || tbl_name ||
         ' set component_type = ''CAPTURE'', '||
         '     sub_component_type = ''CAPTURE SESSION + PS'', '||
         '     position = ' || s_rec.position || ', '||
         '     component_id = ' || s_rec.component_id || ', '||
         '     component_name = ''' || s_rec.component_name || ''' ' ||
         ' where component_type = ''PROPAGATION SENDER'' '||
           ' and path_id = ' || s_rec.path_id ||
           ' and position = ' || ceil(s_rec.position+1) ||
           ' and advisor_run_id = ' || s_rec.advisor_run_id ||
           ' and advisor_run_time = '||
                 'to_date(''' ||
                  to_char(s_rec.advisor_run_time, 'YYYY-MON-DD HH24:MI:SS') ||
                          ''', ''YYYY-MON-DD HH24:MI:SS'')';
        execute immediate stmt2;
        commit;

        -- Delete redundat 'CAPTURE SESSION' at sub-component and session level
        stmt2 :=
         ' delete from ' || tbl_name ||
         ' where component_type = ''CAPTURE'' '||
           ' and sub_component_type = ''CAPTURE SESSION'' '||
           ' and path_id = ' || s_rec.path_id ||
           ' and position = ' || s_rec.position ||
           ' and advisor_run_id = ' || s_rec.advisor_run_id ||
           ' and advisor_run_time = '||
                 'to_date(''' ||
                  to_char(s_rec.advisor_run_time, 'YYYY-MON-DD HH24:MI:SS') ||
                          ''', ''YYYY-MON-DD HH24:MI:SS'')';
        execute immediate stmt2;
        commit;

        -- Delete redundant 'QUEUE' between 'CAPTURE' and 'PROPAGATION SENDER'
        stmt2 :=
         ' delete from ' || tbl_name ||
         ' where component_type = ''QUEUE'' '||
           ' and path_id = ' || s_rec.path_id ||
           ' and position = ' || ceil(s_rec.position) ||
           ' and advisor_run_id = ' || s_rec.advisor_run_id ||
           ' and advisor_run_time = '||
                 'to_date(''' ||
                  to_char(s_rec.advisor_run_time, 'YYYY-MON-DD HH24:MI:SS') ||
                          ''', ''YYYY-MON-DD HH24:MI:SS'')';
        execute immediate stmt2;
        commit;

        -- Update capture name to <capture>=><destination database>
        stmt2 :=
         ' update ' || tbl_name ||
         ' set component_name = ''' || s_rec.component_name || '''' ||
         ' where component_type = ''CAPTURE'' '||
           ' and path_id = ' || s_rec.path_id ||
           ' and advisor_run_id = ' || s_rec.advisor_run_id ||
           ' and advisor_run_time = '||
                 'to_date(''' ||
                  to_char(s_rec.advisor_run_time, 'YYYY-MON-DD HH24:MI:SS') ||
                          ''', ''YYYY-MON-DD HH24:MI:SS'')';
        execute immediate stmt2;
        commit;

      end loop;
      close s_cur;
    exception when others then
      if s_cur%isopen then
        close s_cur;
      end if;
      raise;
    end;

  end PREPARE_CAP_PS;

  ----------------------------------------------------------------------------=
  -- COLLECT_COMP_STAT 
  --   Collect component statistics for all active stream paths.
  ----------------------------------------------------------------------------=
  procedure COLLECT_COMP_STAT(
    run_id   in number,
    tbl_name in varchar2) as
    stmt           varchar2(4000) := null;
    cur_handle     number;
    rows_processed number;
  begin
   dbms_output.put_line('COLLECT_COMP_STAT(tbl_name=>'|| 
     tbl_name||') {');

    stmt :=
    ' insert into ' || tbl_name || '( '||
             ' advisor_run_id, '||
             ' advisor_run_time, '||
             ' path_id, '||
             ' position, '||
             ' component_id, '||
             ' component_name, '||
             ' component_type, '||
             ' sub_component_type, '||
             ' session_id, '||
             ' session_serial#, '||
             ' statistic_alias, '||
             ' statistic_name, '||
             ' statistic_value, '||
             ' statistic_unit ) '||
    'values(:1, :2, :3, :4, :5, :6, :7, :8, :9, :10, :11, :12, :13, :14)';
    
    cur_handle := dbms_sql.open_cursor;
    dbms_sql.parse(cur_handle, stmt, DBMS_SQL.NATIVE);
    
    -- Fix Bug 7508507 by reducing sharable memory use.
    -- Split the original gigantic query into smaller pieces
    -- so that cursors will consume less sharable memory.
    --
    -- The dbms_sql is used to bind individual component statistics
    -- for fast repeated execution.
    --
    
    -- Need alias statistics with 'Sn' to position them in proper order.
    for stat_rec in (
      select p.advisor_run_id,
             P.advisor_run_time,
             P.path_id,
             P.position,
             P.component_id,
             P.component_name,
             P.component_type,
             S.sub_component_type,
             S.session_id,
             S.session_serial#,
             S.statistic_alias,
             S.statistic_name,
             S.statistic_value,
             S.statistic_unit
      from
      ( select P1.advisor_run_id,
               P1.advisor_run_time,
               P1.path_id,
               P2.position,
               P2.component_id,
               P2.component_name,
               P2.component_type
        from ( select distinct
                      path_id,
                      advisor_run_id,
                      advisor_run_time
               from dba_streams_tp_path_stat
               where advisor_run_id = run_id) P1,
             ( select path_id,
                      source_component_id   as component_id,
                      source_component_name as component_name,
                      source_component_type as component_type,
                      position
               from dba_streams_tp_component_link
               where position = 1
               union
               select path_id,
                      destination_component_id   as component_id,
                      destination_component_name as component_name,
                      destination_component_type as component_type,
                      (position + 1)             as position
               from dba_streams_tp_component_link ) P2
        where P1.path_id = P2.path_id ) P,
      ( select distinct 
               component_id,
               sub_component_type,
               session_id,
               session_serial#,
               decode(statistic_name,
                 'IDLE',                          'S5',
                 'FLOW CONTROL',                  'S6',
                 decode(component_type,
                   'CAPTURE', decode(statistic_name,
                        'CAPTURE RATE',           'S1',
                        'ENQUEUE RATE',           'S2',
                        'LATENCY',                'S3', 'S4'),
                   'APPLY', decode(statistic_name,
                        'MESSAGE APPLY RATE',     'S1',
                        'TRANSACTION APPLY RATE', 'S2',
                        'LATENCY',                'S3', 'S4'),
                   'EXTRACT', decode(statistic_name,
                        'SEND RATE',              'S1',  
                        'BYTES SEND RATE',        'S2',
                        'LATENCY',                'S3', 'S4'),
                   'REPLICAT', decode(statistic_name,
                        'MESSAGE RECEIVE RATE',   'S1',
                        'BYTES RECEIVE RATE',     'S2', 'S4'),
                   'QUEUE', decode(statistic_name,
                        'ENQUEUE RATE',           'S1',
                        'SPILL RATE',             'S2',
                        'CURRENT QUEUE SIZE',     'S3', 'S4'),
                   'PROPAGATION SENDER', decode(statistic_name,
                        'SEND RATE',              'S1',
                        'BANDWIDTH',              'S2',
                        'LATENCY',                'S3', 'S4'),
                    'S0') ) as statistic_alias,
               statistic_name,
               statistic_value,
               statistic_unit
        from dba_streams_tp_component_stat
        where statistic_name in (
               'LATENCY', 'IDLE', 'FLOW CONTROL',
               'CAPTURE RATE', 'ENQUEUE RATE',
               'MESSAGE APPLY RATE', 'TRANSACTION APPLY RATE',
               'ENQUEUE RATE', 'SPILL RATE', 'CURRENT QUEUE SIZE',
               'SEND RATE', 'BYTES SEND RATE', 'BANDWIDTH', 'MESSAGE RECEIVE RATE', 
               'BYTES RECEIVE RATE')
          and advisor_run_id = run_id) S
      where P.component_id = S.component_id )
    loop
      dbms_sql.bind_variable(cur_handle, ':1',  stat_rec.advisor_run_id);
      dbms_sql.bind_variable(cur_handle, ':2',  stat_rec.advisor_run_time);
      dbms_sql.bind_variable(cur_handle, ':3',  stat_rec.path_id);
      dbms_sql.bind_variable(cur_handle, ':4',  stat_rec.position);
      dbms_sql.bind_variable(cur_handle, ':5',  stat_rec.component_id);
      dbms_sql.bind_variable(cur_handle, ':6',  stat_rec.component_name);
      dbms_sql.bind_variable(cur_handle, ':7',  stat_rec.component_type);
      dbms_sql.bind_variable(cur_handle, ':8',  stat_rec.sub_component_type);
      dbms_sql.bind_variable(cur_handle, ':9',  stat_rec.session_id);
      dbms_sql.bind_variable(cur_handle, ':10', stat_rec.session_serial#);
      dbms_sql.bind_variable(cur_handle, ':11', stat_rec.statistic_alias);
      dbms_sql.bind_variable(cur_handle, ':12', stat_rec.statistic_name);
      dbms_sql.bind_variable(cur_handle, ':13', stat_rec.statistic_value);
      dbms_sql.bind_variable(cur_handle, ':14', stat_rec.statistic_unit);
        
      rows_processed := dbms_sql.execute(cur_handle);
    end loop;
    
    -- S4 (PARALLELISM) FOR 'APPLY SERVER'
    -- S4 (PARALLELISM) FOR 'LOGMINER PREPARER'
    for stat_rec in (
      select p.advisor_run_id,
             P.advisor_run_time,
             P.path_id,
             P.position,
             P.component_id,
             P.component_name,
             P.component_type,
             S.sub_component_type,
             S.session_id,
             S.session_serial#,
             S.statistic_alias,
             S.statistic_name,
             S.statistic_value,
             S.statistic_unit
      from
      ( select P1.advisor_run_id,
               P1.advisor_run_time,
               P1.path_id,
               P2.position,
               P2.component_id,
               P2.component_name,
               P2.component_type
        from ( select distinct
                      path_id,
                      advisor_run_id,
                      advisor_run_time
               from dba_streams_tp_path_stat
               where advisor_run_id = run_id) P1,
             ( select path_id,
                      source_component_id   as component_id,
                      source_component_name as component_name,
                      source_component_type as component_type,
                      position
               from dba_streams_tp_component_link
               where position = 1
               union
               select path_id,
                      destination_component_id   as component_id,
                      destination_component_name as component_name,
                      destination_component_type as component_type,
                      (position + 1)             as position
               from dba_streams_tp_component_link ) P2
        where P1.path_id = P2.path_id ) P,
      ( select component_id,
               decode(component_type,
                      'APPLY', 'APPLY SERVER', 'LOGMINER PREPARER')
                                     as sub_component_type,
               NULL                  as session_id,
               NULL                  as session_serial#,
               'S4'                  as statistic_alias,
               prop_name             as statistic_name,
               to_number(prop_value) as statistic_value,
               'NUMBER'              as statistic_unit
        from "_DBA_STREAMS_TP_COMPONENT_PROP"
        where prop_name = 'PARALLELISM'
          and component_type in ('APPLY', 'CAPTURE') ) S
      where P.component_id = S.component_id )
    loop
      dbms_sql.bind_variable(cur_handle, ':1',  stat_rec.advisor_run_id);
      dbms_sql.bind_variable(cur_handle, ':2',  stat_rec.advisor_run_time);
      dbms_sql.bind_variable(cur_handle, ':3',  stat_rec.path_id);
      dbms_sql.bind_variable(cur_handle, ':4',  stat_rec.position);
      dbms_sql.bind_variable(cur_handle, ':5',  stat_rec.component_id);
      dbms_sql.bind_variable(cur_handle, ':6',  stat_rec.component_name);
      dbms_sql.bind_variable(cur_handle, ':7',  stat_rec.component_type);
      dbms_sql.bind_variable(cur_handle, ':8',  stat_rec.sub_component_type);
      dbms_sql.bind_variable(cur_handle, ':9',  stat_rec.session_id);
      dbms_sql.bind_variable(cur_handle, ':10', stat_rec.session_serial#);
      dbms_sql.bind_variable(cur_handle, ':11', stat_rec.statistic_alias);
      dbms_sql.bind_variable(cur_handle, ':12', stat_rec.statistic_name);
      dbms_sql.bind_variable(cur_handle, ':13', stat_rec.statistic_value);
      dbms_sql.bind_variable(cur_handle, ':14', stat_rec.statistic_unit);
        
      rows_processed := dbms_sql.execute(cur_handle);
    end loop;
    
    -- S5 (IDLE%)
    -- S6 (FLOW CONTROL%)
    for stat_rec in (
      select p.advisor_run_id,
             P.advisor_run_time,
             P.path_id,
             P.position,
             P.component_id,
             P.component_name,
             P.component_type,
             S.sub_component_type,
             S.session_id,
             S.session_serial#,
             S.statistic_alias,
             S.statistic_name,
             S.statistic_value,
             S.statistic_unit
      from
      ( select P1.advisor_run_id,
               P1.advisor_run_time,
               P1.path_id,
               P2.position,
               P2.component_id,
               P2.component_name,
               P2.component_type
        from ( select distinct
                      path_id,
                      advisor_run_id,
                      advisor_run_time
               from dba_streams_tp_path_stat
               where advisor_run_id = run_id) P1,
             ( select path_id,
                      source_component_id   as component_id,
                      source_component_name as component_name,
                      source_component_type as component_type,
                      position
               from dba_streams_tp_component_link
               where position = 1
               union
               select path_id,
                      destination_component_id   as component_id,
                      destination_component_name as component_name,
                      destination_component_type as component_type,
                      (position + 1)             as position
               from dba_streams_tp_component_link ) P2
        where P1.path_id = P2.path_id ) P,
      ( select component_id,
               sub_component_type,
               NULL as session_id ,
               NULL as session_serial#,
               decode(statistic_name, 'IDLE', 'S5', 'S6')
                                    as statistic_alias,
               statistic_name       as statistic_name,
               SUM(statistic_value) as statistic_value,
               statistic_unit       as statistic_unit
        from dba_streams_tp_component_stat
        where statistic_name in ('IDLE', 'FLOW CONTROL')
          and session_id is not null
          and session_serial# is not null
          and advisor_run_id = run_id
        group by component_id,
                 sub_component_type,
                 statistic_name, statistic_unit ) S
      where P.component_id = S.component_id )
    loop
      dbms_sql.bind_variable(cur_handle, ':1',  stat_rec.advisor_run_id);
      dbms_sql.bind_variable(cur_handle, ':2',  stat_rec.advisor_run_time);
      dbms_sql.bind_variable(cur_handle, ':3',  stat_rec.path_id);
      dbms_sql.bind_variable(cur_handle, ':4',  stat_rec.position);
      dbms_sql.bind_variable(cur_handle, ':5',  stat_rec.component_id);
      dbms_sql.bind_variable(cur_handle, ':6',  stat_rec.component_name);
      dbms_sql.bind_variable(cur_handle, ':7',  stat_rec.component_type);
      dbms_sql.bind_variable(cur_handle, ':8',  stat_rec.sub_component_type);
      dbms_sql.bind_variable(cur_handle, ':9',  stat_rec.session_id);
      dbms_sql.bind_variable(cur_handle, ':10', stat_rec.session_serial#);
      dbms_sql.bind_variable(cur_handle, ':11', stat_rec.statistic_alias);
      dbms_sql.bind_variable(cur_handle, ':12', stat_rec.statistic_name);
      dbms_sql.bind_variable(cur_handle, ':13', stat_rec.statistic_value);
      dbms_sql.bind_variable(cur_handle, ':14', stat_rec.statistic_unit);
        
      rows_processed := dbms_sql.execute(cur_handle);
    end loop;

    -- S7 (TOP EVENT%) FOR SESSION
    for stat_rec in (
      select p.advisor_run_id,
             P.advisor_run_time,
             P.path_id,
             P.position,
             P.component_id,
             P.component_name,
             P.component_type,
             S.sub_component_type,
             S.session_id,
             S.session_serial#,
             S.statistic_alias,
             S.statistic_name,
             S.statistic_value,
             S.statistic_unit
      from
      ( select P1.advisor_run_id,
               P1.advisor_run_time,
               P1.path_id,
               P2.position,
               P2.component_id,
               P2.component_name,
               P2.component_type
        from ( select distinct
                      path_id,
                      advisor_run_id,
                      advisor_run_time
               from dba_streams_tp_path_stat
               where advisor_run_id = run_id) P1,
             ( select path_id,
                      source_component_id   as component_id,
                      source_component_name as component_name,
                      source_component_type as component_type,
                      position
               from dba_streams_tp_component_link
               where position = 1
               union
               select path_id,
                      destination_component_id   as component_id,
                      destination_component_name as component_name,
                      destination_component_type as component_type,
                      (position + 1)             as position
               from dba_streams_tp_component_link ) P2
        where P1.path_id = P2.path_id ) P,
      ( select component_id,
               sub_component_type,
               session_id,
               session_serial#,
               'S7' as statistic_alias,
               regexp_replace(statistic_name, '^EVENT: ', NULL)
                    as statistic_name,
               statistic_value,
               statistic_unit
        from ( select component_id,
                      sub_component_type,
                      session_id,
                      session_serial#,
                      statistic_name,
                      statistic_value,
                      statistic_unit,
                      max(statistic_value)
                        over (partition by
                              component_id, sub_component_type,
                              session_id, session_serial#)
                        as max_statistic_value
               from dba_streams_tp_component_stat
               where statistic_name LIKE 'EVENT: %'
                 and session_id is not null
                 and session_serial# is not null
                 and advisor_run_id = run_id )
        where statistic_value = max_statistic_value ) S
      where P.component_id = S.component_id )
    loop
      dbms_sql.bind_variable(cur_handle, ':1',  stat_rec.advisor_run_id);
      dbms_sql.bind_variable(cur_handle, ':2',  stat_rec.advisor_run_time);
      dbms_sql.bind_variable(cur_handle, ':3',  stat_rec.path_id);
      dbms_sql.bind_variable(cur_handle, ':4',  stat_rec.position);
      dbms_sql.bind_variable(cur_handle, ':5',  stat_rec.component_id);
      dbms_sql.bind_variable(cur_handle, ':6',  stat_rec.component_name);
      dbms_sql.bind_variable(cur_handle, ':7',  stat_rec.component_type);
      dbms_sql.bind_variable(cur_handle, ':8',  stat_rec.sub_component_type);
      dbms_sql.bind_variable(cur_handle, ':9',  stat_rec.session_id);
      dbms_sql.bind_variable(cur_handle, ':10', stat_rec.session_serial#);
      dbms_sql.bind_variable(cur_handle, ':11', stat_rec.statistic_alias);
      dbms_sql.bind_variable(cur_handle, ':12', stat_rec.statistic_name);
      dbms_sql.bind_variable(cur_handle, ':13', stat_rec.statistic_value);
      dbms_sql.bind_variable(cur_handle, ':14', stat_rec.statistic_unit);
        
      rows_processed := dbms_sql.execute(cur_handle);
    end loop;
    
    -- S7 (TOP EVENT%) FOR SUB_COMPONENT
    -- S7 (TOP EVENT%) TOP_LEVEL COMPONENT WITHOUT SUB_COMPONENT
    for stat_rec in (
      select p.advisor_run_id,
             P.advisor_run_time,
             P.path_id,
             P.position,
             P.component_id,
             P.component_name,
             P.component_type,
             S.sub_component_type,
             S.session_id,
             S.session_serial#,
             S.statistic_alias,
             S.statistic_name,
             S.statistic_value,
             S.statistic_unit
      from
      ( select P1.advisor_run_id,
               P1.advisor_run_time,
               P1.path_id,
               P2.position,
               P2.component_id,
               P2.component_name,
               P2.component_type
        from ( select distinct
                      path_id,
                      advisor_run_id,
                      advisor_run_time
               from dba_streams_tp_path_stat
               where advisor_run_id = run_id) P1,
             ( select path_id,
                      source_component_id   as component_id,
                      source_component_name as component_name,
                      source_component_type as component_type,
                      position
               from dba_streams_tp_component_link
               where position = 1
               union
               select path_id,
                      destination_component_id   as component_id,
                      destination_component_name as component_name,
                      destination_component_type as component_type,
                      (position + 1)             as position
               from dba_streams_tp_component_link ) P2
        where P1.path_id = P2.path_id ) P,
      ( select distinct 
               component_id,
               sub_component_type,
               null   as session_id,
               null   as session_serial#,
               'S7'   as statistic_alias,
               regexp_replace(statistic_name, '^EVENT: ', NULL)
                      as statistic_name,
               statistic_value,
               statistic_unit
        from ( select component_id,
                      sub_component_type,
                      statistic_name,
                      statistic_value,
                      statistic_unit,
                      max(statistic_value)
                        over (partition by
                              component_id, sub_component_type)
                        as max_statistic_value
               from dba_streams_tp_component_stat
               where statistic_name LIKE 'EVENT: %'
                 and session_id is not null
                 and session_serial# is not null
                 and advisor_run_id = run_id )
        where statistic_value = max_statistic_value ) S
      where P.component_id = S.component_id )
    loop
      dbms_sql.bind_variable(cur_handle, ':1',  stat_rec.advisor_run_id);
      dbms_sql.bind_variable(cur_handle, ':2',  stat_rec.advisor_run_time);
      dbms_sql.bind_variable(cur_handle, ':3',  stat_rec.path_id);
      dbms_sql.bind_variable(cur_handle, ':4',  stat_rec.position);
      dbms_sql.bind_variable(cur_handle, ':5',  stat_rec.component_id);
      dbms_sql.bind_variable(cur_handle, ':6',  stat_rec.component_name);
      dbms_sql.bind_variable(cur_handle, ':7',  stat_rec.component_type);
      dbms_sql.bind_variable(cur_handle, ':8',  stat_rec.sub_component_type);
      dbms_sql.bind_variable(cur_handle, ':9',  stat_rec.session_id);
      dbms_sql.bind_variable(cur_handle, ':10', stat_rec.session_serial#);
      dbms_sql.bind_variable(cur_handle, ':11', stat_rec.statistic_alias);
      dbms_sql.bind_variable(cur_handle, ':12', stat_rec.statistic_name);
      dbms_sql.bind_variable(cur_handle, ':13', stat_rec.statistic_value);
      dbms_sql.bind_variable(cur_handle, ':14', stat_rec.statistic_unit);
        
      rows_processed := dbms_sql.execute(cur_handle);
    end loop;
    
    -- FILL IN S7 (TOP EVENT%) IF MISSING FOR SESSION
    for stat_rec in (
      select p.advisor_run_id,
             P.advisor_run_time,
             P.path_id,
             P.position,
             P.component_id,
             P.component_name,
             P.component_type,
             S.sub_component_type,
             S.session_id,
             S.session_serial#,
             S.statistic_alias,
             S.statistic_name,
             S.statistic_value,
             S.statistic_unit
      from
      ( select P1.advisor_run_id,
               P1.advisor_run_time,
               P1.path_id,
               P2.position,
               P2.component_id,
               P2.component_name,
               P2.component_type
        from ( select distinct
                      path_id,
                      advisor_run_id,
                      advisor_run_time
               from dba_streams_tp_path_stat
               where advisor_run_id = run_id) P1,
             ( select path_id,
                      source_component_id   as component_id,
                      source_component_name as component_name,
                      source_component_type as component_type,
                      position
               from dba_streams_tp_component_link
               where position = 1
               union
               select path_id,
                      destination_component_id   as component_id,
                      destination_component_name as component_name,
                      destination_component_type as component_type,
                      (position + 1)             as position
               from dba_streams_tp_component_link ) P2
        where P1.path_id = P2.path_id ) P,
      ( select distinct
               component_id,
               sub_component_type,
               session_id,
               session_serial#,
               'S7'           as statistic_alias,
               NULL           as statistic_name,
               0              as statistic_value,
               'PERCENT'      as statistic_unit
        from dba_streams_tp_component_stat S1
        where S1.component_type != 'QUEUE'
          and S1.session_id is not null
          and S1.session_serial# is not null
          and S1.advisor_run_id = run_id and
              not exists (
              select component_id
              from dba_streams_tp_component_stat S2
              where S2.statistic_name like 'EVENT: %' and
                    S1.session_serial# = S2.session_serial# and
                    S1.session_id = S2.session_id and
                    S1.component_id = S2.component_id and
                    S1.advisor_run_id = S2.advisor_run_id ) ) S
      where P.component_id = S.component_id )
    loop
      dbms_sql.bind_variable(cur_handle, ':1',  stat_rec.advisor_run_id);
      dbms_sql.bind_variable(cur_handle, ':2',  stat_rec.advisor_run_time);
      dbms_sql.bind_variable(cur_handle, ':3',  stat_rec.path_id);
      dbms_sql.bind_variable(cur_handle, ':4',  stat_rec.position);
      dbms_sql.bind_variable(cur_handle, ':5',  stat_rec.component_id);
      dbms_sql.bind_variable(cur_handle, ':6',  stat_rec.component_name);
      dbms_sql.bind_variable(cur_handle, ':7',  stat_rec.component_type);
      dbms_sql.bind_variable(cur_handle, ':8',  stat_rec.sub_component_type);
      dbms_sql.bind_variable(cur_handle, ':9',  stat_rec.session_id);
      dbms_sql.bind_variable(cur_handle, ':10', stat_rec.session_serial#);
      dbms_sql.bind_variable(cur_handle, ':11', stat_rec.statistic_alias);
      dbms_sql.bind_variable(cur_handle, ':12', stat_rec.statistic_name);
      dbms_sql.bind_variable(cur_handle, ':13', stat_rec.statistic_value);
      dbms_sql.bind_variable(cur_handle, ':14', stat_rec.statistic_unit);
        
      rows_processed := dbms_sql.execute(cur_handle);
    end loop;
    
    -- FILL IN S7 (TOP EVENT%) IF MISSING FOR SUB_COMPONENT_TYPE
    for stat_rec in (
      select p.advisor_run_id,
             P.advisor_run_time,
             P.path_id,
             P.position,
             P.component_id,
             P.component_name,
             P.component_type,
             S.sub_component_type,
             S.session_id,
             S.session_serial#,
             S.statistic_alias,
             S.statistic_name,
             S.statistic_value,
             S.statistic_unit
      from
      ( select P1.advisor_run_id,
               P1.advisor_run_time,
               P1.path_id,
               P2.position,
               P2.component_id,
               P2.component_name,
               P2.component_type
        from ( select distinct
                      path_id,
                      advisor_run_id,
                      advisor_run_time
               from dba_streams_tp_path_stat
               where advisor_run_id = run_id) P1,
             ( select path_id,
                      source_component_id   as component_id,
                      source_component_name as component_name,
                      source_component_type as component_type,
                      position
               from dba_streams_tp_component_link
               where position = 1
               union
               select path_id,
                      destination_component_id   as component_id,
                      destination_component_name as component_name,
                      destination_component_type as component_type,
                      (position + 1)             as position
               from dba_streams_tp_component_link ) P2
        where P1.path_id = P2.path_id ) P,
      ( select distinct
               component_id,
               sub_component_type,
               NULL           as session_id,
               NULL           as session_serial#,
               'S7'           as statistic_alias,
               NULL           as statistic_name,
               0              as statistic_value,
               'PERCENT'      as statistic_unit
        from dba_streams_tp_component_stat S1
        where S1.component_type in ('CAPTURE', 'APPLY', 'EXTRACT', 'REPLICAT')
          and S1.sub_component_type is not null
          and S1.advisor_run_id = run_id
          and not exists (
                select component_id
                from dba_streams_tp_component_stat S2
                where S2.statistic_name like 'EVENT: %'
                  and S1.sub_component_type = S2.sub_component_type
                  and S1.component_id = S2.component_id
                  and S1.advisor_run_id = S2.advisor_run_id ) ) S
      where P.component_id = S.component_id )
    loop
      dbms_sql.bind_variable(cur_handle, ':1',  stat_rec.advisor_run_id);
      dbms_sql.bind_variable(cur_handle, ':2',  stat_rec.advisor_run_time);
      dbms_sql.bind_variable(cur_handle, ':3',  stat_rec.path_id);
      dbms_sql.bind_variable(cur_handle, ':4',  stat_rec.position);
      dbms_sql.bind_variable(cur_handle, ':5',  stat_rec.component_id);
      dbms_sql.bind_variable(cur_handle, ':6',  stat_rec.component_name);
      dbms_sql.bind_variable(cur_handle, ':7',  stat_rec.component_type);
      dbms_sql.bind_variable(cur_handle, ':8',  stat_rec.sub_component_type);
      dbms_sql.bind_variable(cur_handle, ':9',  stat_rec.session_id);
      dbms_sql.bind_variable(cur_handle, ':10', stat_rec.session_serial#);
      dbms_sql.bind_variable(cur_handle, ':11', stat_rec.statistic_alias);
      dbms_sql.bind_variable(cur_handle, ':12', stat_rec.statistic_name);
      dbms_sql.bind_variable(cur_handle, ':13', stat_rec.statistic_value);
      dbms_sql.bind_variable(cur_handle, ':14', stat_rec.statistic_unit);
        
      rows_processed := dbms_sql.execute(cur_handle);
    end loop;
    
    -- FILL IN S7 (TOP EVENT%) IF MISSING FOR TOP_LEVEL COMPONENT
    for stat_rec in (
      select p.advisor_run_id,
             P.advisor_run_time,
             P.path_id,
             P.position,
             P.component_id,
             P.component_name,
             P.component_type,
             S.sub_component_type,
             S.session_id,
             S.session_serial#,
             S.statistic_alias,
             S.statistic_name,
             S.statistic_value,
             S.statistic_unit
      from
      ( select P1.advisor_run_id,
               P1.advisor_run_time,
               P1.path_id,
               P2.position,
               P2.component_id,
               P2.component_name,
               P2.component_type
        from ( select distinct
                      path_id,
                      advisor_run_id,
                      advisor_run_time
               from dba_streams_tp_path_stat
               where advisor_run_id = run_id) P1,
             ( select path_id,
                      source_component_id   as component_id,
                      source_component_name as component_name,
                      source_component_type as component_type,
                      position
               from dba_streams_tp_component_link
               where position = 1
               union
               select path_id,
                      destination_component_id   as component_id,
                      destination_component_name as component_name,
                      destination_component_type as component_type,
                      (position + 1)             as position
               from dba_streams_tp_component_link ) P2
        where P1.path_id = P2.path_id ) P,
      ( select distinct
               component_id,
               NULL         as sub_component_type,
               NULL         as session_id,
               NULL         as session_serial#,
               'S7'         as statistic_alias,
               NULL         as statistic_name,
               0            as statistic_value,
               'PERCENT'    as statistic_unit
        from dba_streams_tp_component_stat S1
        where S1.component_type not in ('CAPTURE', 'APPLY', 'QUEUE', 'EXTRACT', 'REPLICAT')
          and S1.sub_component_type is null
          and S1.advisor_run_id = run_id
          and not exists (
                select component_id
                from dba_streams_tp_component_stat S2
                where S2.statistic_name like 'EVENT: %'
                  and S1.component_id = S2.component_id
                  and S1.advisor_run_id = S2.advisor_run_id ) ) S
      where P.component_id = S.component_id )
    loop
      dbms_sql.bind_variable(cur_handle, ':1',  stat_rec.advisor_run_id);
      dbms_sql.bind_variable(cur_handle, ':2',  stat_rec.advisor_run_time);
      dbms_sql.bind_variable(cur_handle, ':3',  stat_rec.path_id);
      dbms_sql.bind_variable(cur_handle, ':4',  stat_rec.position);
      dbms_sql.bind_variable(cur_handle, ':5',  stat_rec.component_id);
      dbms_sql.bind_variable(cur_handle, ':6',  stat_rec.component_name);
      dbms_sql.bind_variable(cur_handle, ':7',  stat_rec.component_type);
      dbms_sql.bind_variable(cur_handle, ':8',  stat_rec.sub_component_type);
      dbms_sql.bind_variable(cur_handle, ':9',  stat_rec.session_id);
      dbms_sql.bind_variable(cur_handle, ':10', stat_rec.session_serial#);
      dbms_sql.bind_variable(cur_handle, ':11', stat_rec.statistic_alias);
      dbms_sql.bind_variable(cur_handle, ':12', stat_rec.statistic_name);
      dbms_sql.bind_variable(cur_handle, ':13', stat_rec.statistic_value);
      dbms_sql.bind_variable(cur_handle, ':14', stat_rec.statistic_unit);
        
      rows_processed := dbms_sql.execute(cur_handle);
    end loop;
    
    -- Close cursor
    dbms_sql.close_cursor(cur_handle);

    -- Update position for statistics at different levels.
    -- Positions of sub-components of a top level component are
    -- determined according to the direction streams data flow.
    stmt :=
     ' update ' || tbl_name ||
     ' set position = '||
           ' floor(position) + '||
           ' decode(sub_component_type, '||
           '   ''LOGMINER READER'',        0.1, '||
           '   ''LOGMINER PREPARER'',      0.2, '||
           '   ''LOGMINER BUILDER'',       0.3, '||
           '   ''CAPTURE SESSION'',        0.4, '||
           '   ''PROPAGATION SENDER+RECEIVER'', 0.1, '||
           '   ''APPLY READER'',           0.2, '||
           '   ''APPLY COORDINATOR'',      0.3, '||
           '   ''APPLY SERVER'',           0.4, '||
           '   0.0) '||
     ' where (advisor_run_id, advisor_run_time) in '||
           ' ( select distinct advisor_run_id, advisor_run_time '||
           '   from dba_streams_tp_component_stat '||
           '   where advisor_run_id = ' || run_id || ') ';
    execute immediate stmt;
    commit;

    -- TODO this may be not needed anymore since component_level
    -- statistics calculation now has a 'distinct' 
    -- Delete duplicate TOP EVENTs with the same percentage
    stmt :=
      ' delete from ' || tbl_name || ' A '||
      ' where rowid > ( '||
      '   select MIN(rowid) from ' || tbl_name || ' B '||
      '   where A.advisor_run_id = B.advisor_run_id and '||
      '         A.advisor_run_time = B.advisor_run_time and '||
      '         A.path_id = B.path_id and '||
      '         A.position = B.position and '||
      '         A.component_id = B.component_id and '||
      '         A.sub_component_type = B.sub_component_type and '||
      '         A.session_id = B.session_id and '||
      '         A.session_serial# = B.session_serial# and '||
      '         A.statistic_alias = B.statistic_alias and '||
      '         A.statistic_value = B.statistic_value ) '||
      '   and statistic_alias = ''S7'' '||
      '   and ( advisor_run_id, advisor_run_time ) in '||
      '       ( select distinct advisor_run_id, advisor_run_time '||
      '         from dba_streams_tp_component_stat '||
      '         where advisor_run_id = '|| run_id ||')';
    execute immediate stmt;
    commit;

    -- Preare CCA statistics for displaying 'CAP+PS'
    PREPARE_CAP_PS(run_id, tbl_name);

    dbms_output.put_line('COLLECT_COMP_STAT}');

  end COLLECT_COMP_STAT;

  ----------------------------------------------------------------------------=
  -- GET_BOTTLENECK
  --   Get bottleneck information in the following form:
  --     |<B> advisor_run_reason
  --   OR
  --     |<B> component_name sub_component_name sid serial topevent% "topevent"
  ----------------------------------------------------------------------------=
  function GET_BOTTLENECK(run_id in number, path_id in number)
  return varchar2 as
    val  varchar2(4000) := null; 
    val2 varchar2(4000) := null;
    bott DBA_STREAMS_TP_COMPONENT_STAT%ROWTYPE;
  begin
    begin
      for bott in (
        select distinct S.*
        from dba_streams_tp_path_bottleneck B,
           ( select distinct *
             from dba_streams_tp_component_stat
             where statistic_name LIKE 'EVENT: %' and
                   session_id is not null and
                   session_serial# is not null and
                   advisor_run_id = GET_BOTTLENECK.run_id ) S
        where B.path_id = GET_BOTTLENECK.path_id and
              B.advisor_run_id = GET_BOTTLENECK.run_id and
              B.top_session_id is not null and
              B.top_session_serial# is not null and
              B.bottleneck_identified = 'YES' and
              B.advisor_run_id = S.advisor_run_id (+) and
              B.component_id = S.component_id (+) and
              B.top_session_id = S.session_id (+) and
              B.top_session_serial# = S.session_serial# (+)
        order by S.statistic_value desc)
      loop
        val := '|<B> ' ||
               get_comp_name(bott.component_type, bott.component_name) ||' '||
               case when (bott.component_type = 'APPLY' OR
                          bott.component_type = 'CAPTURE') then
                         get_sub_comp_type(bott.sub_component_type) || ' '
                    else NULL
               end ||
               bott.session_id ||' '|| bott.session_serial# ||' '||
               to_char(bott.statistic_value, 'FM999D9') || '% "' ||
               regexp_replace(bott.statistic_name, '^EVENT: ', NULL) || '"';
        exit when val is not null;
      end loop;

      if val is null then
        -- post process for XStream Bottleneck in case 'EXTERNAL' is 
        -- the bottleneck. In this case, bottleneck_identified is set to 'YES'
        -- but component id/session_id/session_serial will all be NULL and will
        -- not be covered by previous step. 
        select '|<B> "EXTERNAL"' into val2
        from dba_streams_tp_path_bottleneck
        where advisor_run_id = GET_BOTTLENECK.run_id AND
              path_id = GET_BOTTLENECK.path_id AND
              action_name = 'EXTERNAL' AND
              bottleneck_identified = 'YES';
        
        if val2 is not null then
          val := val2;
        else
          select distinct '|<B> "' || advisor_run_reason || '"' into val
          from dba_streams_tp_path_bottleneck B
          where B.path_id = GET_BOTTLENECK.path_id and
                B.advisor_run_id = GET_BOTTLENECK.run_id;
        end if;
      end if;

    exception when others then
       val := '|<B> ' || 'NO BOTTLENECK IDENTIFIED';
    end;

    return val;
  end GET_BOTTLENECK;

  ----------------------------------------------------------------------------=
  -- COLLECT_PATH_STAT
  --   Collect path statistics by concatenating component statistics into
  --   one single line representation for each active path. The output is
  --   similar to STRMMON output.
  ----------------------------------------------------------------------------=
  procedure COLLECT_PATH_STAT(
    run_id              in number,
    input_tbl           in varchar2,
    output_tbl          in varchar2,
    top_event_threshold in number default 15)
  as
    stmt1 varchar2(4000);
    stmt2 varchar2(4000);

    TYPE cur_type is ref cursor;
    s_cur         cur_type;
    s_rec         stat_type;
    p_rec         stat_type; -- previous s_rec
    p_rec_default stat_type; -- reset p_rec to the default
    path_stat     varchar2(4000) := null;
    TYPE maptype IS TABLE OF boolean INDEX BY VARCHAR2(30);
    topEventMap maptype;
  begin
    stmt1 :=
      ' select advisor_run_id, '||
             ' advisor_run_time, '||
             ' path_id, '||
             ' position, '||
             ' component_id, '||
             ' component_name, '||
             ' component_type, '||
             ' sub_component_type, '||
             ' session_id, '||
             ' session_serial#, '||
             ' statistic_alias, '||
             ' statistic_name, '||
             ' statistic_value, '||
             ' statistic_unit '||
      ' from ' || input_tbl ||
      ' where ( advisor_run_id, advisor_run_time ) in '||
      '       ( select distinct advisor_run_id, advisor_run_time '||
      '         from dba_streams_tp_component_stat '||
      '         where advisor_run_id = '|| run_id ||') and '||
              -- filter out session level statistics
      '       session_id is null and '|| 
      '       session_serial# is null '||  
      ' order by path_id, position, statistic_alias';

    begin
      open s_cur for stmt1;
      loop
        fetch s_cur into s_rec;
        exit when s_cur%notfound;

        if p_rec.path_id = 0 then
           path_stat := null;
           p_rec.path_id := s_rec.path_id;
        end if;

        if p_rec.path_id = s_rec.path_id then
           if path_stat is null then
             path_stat := join_stat(p_rec, s_rec, top_event_threshold);
           else
             if s_rec.statistic_alias = 'S7' and s_rec.sub_component_type in
                ('LOGMINER PREPARER', 'APPLY SERVER') then
               -- check if we have aggregated them already for this path
               if topEventMap.exists(s_rec.sub_component_type) then
                 continue;
               else
                  topEventMap(s_rec.sub_component_type) := TRUE;
               end if;

               -- Aggregate TOP EVENT percentage for LMP and APS
               path_stat := path_stat || ' ' ||
                            aggregate_top_event(run_id,
                                                s_rec.component_id,
                                                s_rec.sub_component_type,
                                                top_event_threshold);
             else
               path_stat := path_stat || ' ' ||
                            join_stat(p_rec, s_rec, top_event_threshold);
             end if;
           end if;
        else
           -- Append bottleneck information
           path_stat := path_stat || ' ' ||
                        get_bottleneck(p_rec.advisor_run_id, p_rec.path_id);

           stmt2 :=
             'insert into ' || output_tbl ||
             '(path_id, advisor_run_id, advisor_run_time, statistics) '||
             'values( '||
                p_rec.path_id || ',' ||
                p_rec.advisor_run_id || ',' ||
                'to_date(''' ||
                to_char(p_rec.advisor_run_time, 'YYYY-MON-DD HH24:MI:SS')||
                         ''', ''YYYY-MON-DD HH24:MI:SS''), ''' ||
                path_stat || ''')';
           execute immediate stmt2;
           commit;

           p_rec := p_rec_default;
           p_rec.path_id := s_rec.path_id;
           path_stat := join_stat(p_rec, s_rec, top_event_threshold);
           topEventMap.delete;
        end if;

        p_rec := s_rec;
      end loop;
      close s_cur;

      if path_stat is not null then
        -- Append bottleneck information
        path_stat := path_stat || ' ' ||
                     get_bottleneck(p_rec.advisor_run_id, p_rec.path_id);

        stmt2 :=
          'insert into ' || output_tbl ||
             '(path_id, advisor_run_id, advisor_run_time, statistics) '||
             'values( '||
                p_rec.path_id || ',' ||
                p_rec.advisor_run_id || ',' ||
                'to_date(''' ||
                to_char(p_rec.advisor_run_time, 'YYYY-MON-DD HH24:MI:SS')||
                         ''', ''YYYY-MON-DD HH24:MI:SS''), ''' ||
                path_stat || ''')';
        execute immediate stmt2;
        commit;
      end if;
    exception when others then
      if s_cur%isopen then
        close s_cur;
      end if;
      raise;
    end;

  end COLLECT_PATH_STAT;
  
  ----------------------------------------------------------------------------=
  -- COLLECT_PATH_SESS
  --   Collect session level statistics for each active stream path.
  ----------------------------------------------------------------------------=
  procedure COLLECT_PATH_SESS(
    run_id              in number,
    input_tbl           in varchar2,
    output_tbl          in varchar2,
    top_event_threshold in number default 15) as
    stmt1 varchar2(4000);
    stmt2 varchar2(4000);

    TYPE cur_type is ref cursor;
    s_cur         cur_type;
    s_rec         stat_type;
    p_rec         stat_type; -- previous s_rec
    p_rec_default stat_type; -- reset p_rec to the default
    path_sess     varchar2(4000) := null;
  begin
    stmt1 :=
      ' select advisor_run_id, '||
             ' advisor_run_time, '||
             ' path_id, '||
             ' position, '||
             ' component_id, '||
             ' component_name, '||
             ' component_type, '||
             ' sub_component_type, '||
             ' session_id, '||
             ' session_serial#, '||
             ' statistic_alias, '||
             ' statistic_name, '||
             ' statistic_value, '||
             ' statistic_unit '||
      ' from ' || input_tbl ||
      ' where ( advisor_run_id, advisor_run_time ) in '||
      '       ( select distinct advisor_run_id, advisor_run_time '||
      '         from dba_streams_tp_component_stat '||
      '         where advisor_run_id = '|| run_id ||') and '||
              -- select session level statistics only
      '       session_id is not null and '|| 
      '       session_serial# is not null '|| 
      ' order by path_id,position,session_id,session_serial#,statistic_alias';

    begin
      open s_cur for stmt1;
      loop
        fetch s_cur into s_rec;
        exit when s_cur%notfound;

        if p_rec.path_id = 0 then
           path_sess := null;
           p_rec.path_id := s_rec.path_id;
        end if;

        if p_rec.path_id = s_rec.path_id then
           if path_sess is null then
             path_sess := join_stat(p_rec, s_rec, top_event_threshold, TRUE);
           else
             path_sess := path_sess || ' ' ||
                          join_stat(p_rec, s_rec, top_event_threshold, TRUE);
           end if;
        else
           stmt2 :=
            'update ' || output_tbl || ' V set V.session_statistics = '||
            '''' || path_sess || ''' '|| 
            'where (V.advisor_run_id, V.advisor_run_time) in '||
            '      (select distinct advisor_run_id, advisor_run_time '||
            '       from dba_streams_tp_component_stat '||
            '       where advisor_run_id = ' || run_id || ') and '||
            '       V.path_id = ' || p_rec.path_id;
           execute immediate stmt2;
           commit;

           p_rec := p_rec_default;
           p_rec.path_id := s_rec.path_id;
           path_sess := join_stat(p_rec, s_rec, top_event_threshold, TRUE);
        end if;

        p_rec := s_rec;
      end loop;
      close s_cur;

      if path_sess is not null then
        stmt2 :=
          'update ' || output_tbl || ' V set V.session_statistics = '||
          '''' || path_sess || ''' '|| 
          'where (V.advisor_run_id, V.advisor_run_time) in '||
          '      (select distinct advisor_run_id, advisor_run_time '||
          '       from dba_streams_tp_component_stat '||
          '       where advisor_run_id = ' || run_id || ') and '||
          '       V.path_id = ' || p_rec.path_id;
        execute immediate stmt2;
        commit;
      end if;
    exception when others then
      if s_cur%isopen then
        close s_cur;
      end if;
      raise;
    end;

  end COLLECT_PATH_SESS;
  
  ----------------------------------------------------------------------------=
  -- COLLECT_CONTROL_SETTING
  --   Collect statistics control setting for all active stream paths.
  ----------------------------------------------------------------------------=
  procedure COLLECT_CONTROL_SETTING(
    run_id              in number,
    output_tbl          in varchar2,
    setting             in varchar2) as
    stmt varchar2(2400);
  begin
  
    stmt :=
    'update ' || output_tbl || ' V set V.setting = ''' ||setting|| ''' '||
    'where (V.advisor_run_id, V.advisor_run_time) in '||
    '      (select distinct advisor_run_id, advisor_run_time '||
    '       from dba_streams_tp_component_stat '||
    '       where advisor_run_id = ' || run_id || ')';
    execute immediate stmt;
  
    commit;
  end COLLECT_CONTROL_SETTING;
  
  ----------------------------------------------------------------------------=
  -- COLLECT_OPTIMIZATION_MODE
  --   Collect property OPTIMIZATION_MODE for all active stream paths.
  ----------------------------------------------------------------------------=
  procedure COLLECT_OPTIMIZATION_MODE(
    run_id              in number,
    output_tbl          in varchar2) as
    stmt varchar2(2400);
  begin
    -- optimization_mode:
    --   0 non-CCA mode
    --   1 CCA mode
    --   2 CCAC mode

    stmt :=
    'update ' || output_tbl || ' V set V.optimization = '||
    ' ( select STATISTIC_VALUE '||
    '   from dba_streams_tp_path_stat S '||
    '   where statistic_name = ''OPTIMIZATION_MODE'' '||
    '     and S.advisor_run_id = ' || run_id ||
    '     and V.path_id = S.path_id '||
    '     and V.advisor_run_id = S.advisor_run_id '||
    '     and V.advisor_run_time = S.advisor_run_time ) '||
    'where (V.advisor_run_id, V.advisor_run_time) in '||
    '      (select distinct advisor_run_id, advisor_run_time '||
    '       from dba_streams_tp_path_stat '||
    '       where advisor_run_id = ' || run_id || ')';
    execute immediate stmt;
  
    commit;
  end COLLECT_OPTIMIZATION_MODE;

  ----------------------------------------------------------------------------=
  -- PERSIST_LINE_DISPLAY
  --   Persist statics for showing streams performance in line-display format.
  ----------------------------------------------------------------------------=
  procedure PERSIST_LINE_DISPLAY(
    run_id         in number,
    comp_stat_tbl  in varchar2,
    path_stat_tbl  in varchar2,
    raise_error    in boolean default FALSE,
    save_comp_stat in boolean default TRUE)
  as
    param_setting varchar2(2000);
  begin
    -- Get control parameter setting
    param_setting :=
      'TOP_EVENT_THRESHOLD=' ||
         param_top_event_threshold ||
      ' BOTTLENECK_IDLE_THRESHOLD=' ||
         dbms_streams_advisor_adm.get_bottleneck_idle_threshold() ||
      ' BOTTLENECK_FLOWCTRL_THRESHOLD=' ||
         dbms_streams_advisor_adm.get_bottleneck_flowctrl_thresh();
    
    -- Save component statistics
    begin
      COLLECT_COMP_STAT(run_id, comp_stat_tbl);
    exception when others then
      if raise_error then
        raise;
      else
        NULL;
      end if;
    end;
    
    -- Save top-level path statistics
    begin
      COLLECT_PATH_STAT(run_id, comp_stat_tbl, path_stat_tbl,
                        param_top_event_threshold);
    exception when others then
      if raise_error then
        raise;
      else
        NULL;
      end if;
    end;

    -- Save session-level path statistics
    begin
      COLLECT_PATH_SESS(run_id, comp_stat_tbl, path_stat_tbl,
                        param_top_event_threshold);
    exception when others then
      if raise_error then
        raise;
      else
        NULL;
      end if;
    end;
    
    -- Save OPTIMIZATION_MODE property
    begin
      COLLECT_OPTIMIZATION_MODE(run_id, path_stat_tbl);
    exception when others then
      if raise_error then
        raise;
      else
        NULL;
      end if;
    end;
    
    -- Save the control parameter setting
    begin
      COLLECT_CONTROL_SETTING(run_id, path_stat_tbl, param_setting);
    exception when others then
      if raise_error then
        raise;
      else
        NULL;
      end if;
    end;

    if (save_comp_stat = FALSE) then
      delete from streams$_pa_show_comp_stat;
      commit;
    end if;
  
  end PERSIST_LINE_DISPLAY;

  ----------------------------------------------------------------------------=
  -- CHECK_DATABASE_LINKS
  --   Persist database information upon each advisor run.
  ----------------------------------------------------------------------------=
  procedure CHECK_DATABASE_LINKS
  as
    TYPE CUR_TYPE IS ref cursor;
    TYPE DBLINK_TYPE IS RECORD(db_link VARCHAR2(128));
    TYPE DBNAME_TYPE IS RECORD(global_name   varchar2(128),
                               db_unique_name varchar2(128));

    cur        cur_type;
    dbname_rec dbname_type;
    dblink_rec dblink_type;
    canon_user dbms_id;
    stmt       varchar2(4000);
    ecode      number;
    emesg      varchar2(200);
  begin
    canon_user := SYS_CONTEXT('USERENV', 'CURRENT_USER');

    for dblink_rec in (
      select global_name as db_link from global_name
      union
      select db_link from dba_db_links
      where owner = canon_user or username = canon_user)
    loop
      stmt := 'select global_name, db_unique_name from ' ||
              'global_name@' || dblink_rec.db_link ||', '||
              'v$database@'  || dblink_rec.db_link;
      begin
        open cur for stmt;
        loop
          fetch cur into dbname_rec;
          exit when cur%NOTFOUND;

          -- upsert streams$_pa_database
          merge into streams$_pa_database T
          using (select dbname_rec.global_name as global_name from dual) N
          on (T.global_name = N.global_name)
          when matched then
               update set T.error_number = null,
                          T.error_message = null
          when not matched then
               insert (T.global_name)
               values (N.global_name);
          commit;

          -- upsert streams$_pa_database_prop
          merge into streams$_pa_database_prop T
          using (select dbname_rec.global_name    as global_name,
                        'DB_UNIQUE_NAME'          as prop_name,
                        dbname_rec.db_unique_name as prop_value
                 from dual) N
          on (T.global_name = N.global_name and T.prop_name = N.prop_name)
          when matched then
               update set T.prop_value = N.prop_value
          when not matched then
               insert (T.global_name, T.prop_name, T.prop_value)
               values (N.global_name, N.prop_name, N.prop_value);
          commit;
        end loop;

        close cur;
      exception when others then
        close cur;

        ecode := SQLCODE;
        emesg := SQLERRM;

        -- Assume global_name set to TRUE
        update streams$_pa_database
        set error_number = ecode, error_message = emesg
        where global_name = dblink_rec.db_link;
        commit;
      end; -- End processing stmt

    end loop;

  end CHECK_DATABASE_LINKS;

  ----------------------------------------------------------------------------=
  -- LOAD_MONITORING_INFO
  --   Load advisor monitoring info upon each advisor run.
  ----------------------------------------------------------------------------=
  procedure LOAD_MONITORING_INFO
  as
  begin
    begin
      select job_name,
             client_name,
             query_user_name,
             started_time
       into  monitoring_job_name,
             monitoring_client_name,
             monitoring_query_user_name,
             monitoring_started_time
      from streams$_pa_monitoring
      where state = 'STARTED';
    exception when others then
      null;
    end;
  end LOAD_MONITORING_INFO;

  ----------------------------------------------------------------------------=
  -- SAVE_CONTROL_PARAMS
  --   Persist advisor control parameters upon each advisor run.
  ----------------------------------------------------------------------------=
  procedure SAVE_CONTROL_PARAMS(run_id in number, run_time date)
  as
  begin

    -----------------------------------------
    -- Persist advisor control parameters. --
    -----------------------------------------
    -- PARAM_NAME:
    --   INTERVAL
    --   RETENTION_TIME
    --   TOP_EVENT_THRESHOLD
    --   BOTTLENECK_IDLE_THRESHOLD
    --   BOTTLENECK_FLOWCTRL_THRESHOLD

    insert into streams$_pa_control(
           advisor_run_id,
           advisor_run_time,
           param_name,
           param_value,
           param_unit)
    values(run_id,
           run_time,
           'INTERVAL',
           param_interval,
           'SECOND');
    commit;
    
    insert into streams$_pa_control(
           advisor_run_id,
           advisor_run_time,
           param_name,
           param_value,
           param_unit)
    values(run_id,
           run_time,
           'RETENTION_TIME',
           param_retention_time,
           'HOUR');
    commit;

    insert into streams$_pa_control(
           advisor_run_id,
           advisor_run_time,
           param_name,
           param_value,
           param_unit)
    values(run_id,
           run_time,
           'TOP_EVENT_THRESHOLD',
           param_top_event_threshold,
           'PERCENT');
    commit;

    insert into streams$_pa_control(
           advisor_run_id,
           advisor_run_time,
           param_name,
           param_value,
           param_unit)
    values(run_id,
           run_time,
           'BOTTLENECK_IDLE_THRESHOLD',
           param_bot_idle_threshold,
           'PERCENT');
    commit;

    insert into streams$_pa_control(
           advisor_run_id,
           advisor_run_time,
           param_name,
           param_value,
           param_unit)
    values(run_id,
           run_time,
           'BOTTLENECK_FLOWCTRL_THRESHOLD',
           param_bot_flowctrl_threshold,
           'PERCENT');
    commit;

  end SAVE_CONTROL_PARAMS;

  ----------------------------------------------------------------------------=
  -- RECOVER_CONTROL_PARAMS
  --   Load advisor control parameters upon each advisor run.
  ----------------------------------------------------------------------------=
  procedure RECOVER_CONTROL_PARAMS
  as
    cnt number := 0;
  begin
    begin
      select count(*) into cnt
      from streams$_pa_monitoring
      where state = 'STARTED';

      if (cnt = 0) then
        -- Someone manually deletes monitoring information for an active job.
        insert into streams$_pa_monitoring(
               job_name,
               client_name,
               query_user_name,
               started_time,
               altered_time,
               state)
        values(monitoring_job_name,
               monitoring_client_name,
               monitoring_query_user_name,
               monitoring_started_time,
               monitoring_altered_time,
               'STARTED');
        commit;
      end if;
    exception when others then
      null;
    end;

    begin
      select count(advisor_run_id) into cnt
      from streams$_pa_control
      where advisor_run_id = 0;

      if (cnt < total_param_cnt) then
         -- Someone manually deletes some control parameters.
         delete from streams$_pa_control where advisor_run_id = 0;
         commit;
         
         -- update using current control parameters.
         SAVE_CONTROL_PARAMS(0, null);
      end if;
    exception when others then
      null;
    end;

  end RECOVER_CONTROL_PARAMS;

  ----------------------------------------------------------------------------=
  -- LOAD_CONTROL_PARAMS
  --   Load advisor control parameters upon each advisor run.
  ----------------------------------------------------------------------------=
  procedure LOAD_CONTROL_PARAMS
  as
    alt_time timestamp := null;
  begin

    ------------------------------------------------
    -- Check and load advisor control parameters. --
    ------------------------------------------------
    begin
      select nvl(altered_time, started_time) into alt_time
      from streams$_pa_monitoring
      where state = 'STARTED';
    exception when others then
      alt_time := null;
    end;

    if(monitoring_altered_time is null or
       monitoring_altered_time < alt_time) then
      monitoring_altered_time := alt_time;

      begin
        select param_value into param_interval
        from streams$_pa_control
        where advisor_run_id = 0 and
              advisor_run_time is null and
              param_name = 'INTERVAL';
      exception when others then
        null;
      end;

      begin
        select param_value into param_retention_time
        from streams$_pa_control
        where advisor_run_id = 0 and
              advisor_run_time is null and
              param_name = 'RETENTION_TIME';

        -- Set advisor retention_time.
        -- No need to use monitoring API's retention_time
        -- since all we need is to keep the latest advisor run.
        dbms_streams_advisor_adm.set_retention_time(
          param_interval * 0.9 / seconds_per_hour);
      exception when others then
        null;
      end;

      begin
        select param_value into param_top_event_threshold
        from streams$_pa_control
        where advisor_run_id = 0 and
              advisor_run_time is null and
              param_name = 'TOP_EVENT_THRESHOLD';
      exception when others then
        null;
      end;

      begin
        select param_value into param_bot_idle_threshold
        from streams$_pa_control
        where advisor_run_id = 0 and
              advisor_run_time is null and
              param_name = 'BOTTLENECK_IDLE_THRESHOLD';

        -- Set bottleneck_idle_threshold
        dbms_streams_advisor_adm.set_bottleneck_idle_threshold(
          param_bot_idle_threshold);
      exception when others then
        null;
      end;

      begin
        select param_value into param_bot_flowctrl_threshold
        from streams$_pa_control
        where advisor_run_id = 0 and
              advisor_run_time is null and
              param_name = 'BOTTLENECK_FLOWCTRL_THRESHOLD';

        -- bottleneck_flowctrl_threshold
        dbms_streams_advisor_adm.set_bottleneck_flowctrl_thresh(
          param_bot_flowctrl_threshold);
      exception when others then
        null;
      end;
    end if;

  end LOAD_CONTROL_PARAMS;

  ----------------------------------------------------------------------------=
  -- CLEAN_RETENTION
  --   Perform retention cleanup upon each advisor run.
  ----------------------------------------------------------------------------=
  procedure CLEAN_RETENTION(run_time date)
  as
    oldest_time date; 
  begin
    oldest_time := run_time - param_retention_time/24;

    delete from streams$_pa_control
    where advisor_run_time < oldest_time;
    commit;

    delete from streams$_pa_component_stat
    where advisor_run_time < oldest_time;
    commit;

    delete from streams$_pa_path_bottleneck
    where advisor_run_time < oldest_time;
    commit;

    delete from streams$_pa_path_stat
    where advisor_run_time < oldest_time;
    commit;

    delete from streams$_pa_monitoring
    where stopped_time is not null and stopped_time < oldest_time;
    commit;

    delete from streams$_pa_show_comp_stat
    where advisor_run_time < oldest_time;
    commit;

    delete from streams$_pa_show_path_stat
    where advisor_run_time < oldest_time;
    commit;
  end CLEAN_RETENTION;

  ----------------------------------------------------------------------------=
  -- PERSIST_ADVISOR_RUN
  --   Persist statistics for all active stream paths for a given advisor run.
  ----------------------------------------------------------------------------=
  procedure PERSIST_ADVISOR_RUN(run_id in number)
  as
    type cur_type is ref cursor;
    run_time     date := null;
    path_key     varchar2(4000) := null;
    path_id      number;
    path_id_cur  cur_type;
    bottleneck   varchar2(4000) := null;
  begin
    select advisor_run_time into run_time
    from (select max(advisor_run_time) as advisor_run_time
          from DBA_STREAMS_TP_PATH_STAT where advisor_run_id = run_id);

    -- upsert streams$_pa_database.
    merge into streams$_pa_database T
    using (select global_name,
                  last_queried
          from dba_streams_tp_database) N
    on (T.global_name = N.global_name)
    when matched then
         update set T.last_queried = N.last_queried
    when not matched then
         insert (T.global_name, T.last_queried)
         values (N.global_name, N.last_queried);
    commit;

    -- upsert streams$_pa_database_prop.
    merge into streams$_pa_database_prop T
    using (select global_name as global_name,
                  'VERSION'   as prop_name,
                  version     as prop_value
          from dba_streams_tp_database) N
    on (T.global_name = N.global_name and T.prop_name = N.prop_name)
    when matched then
         update set T.prop_value = N.prop_value
    when not matched then
         insert (T.global_name, T.prop_name, T.prop_value)
         values (N.global_name, N.prop_name, N.prop_value);

    merge into streams$_pa_database_prop T
    using (select global_name     as global_name,
                  'COMPATIBILITY' as prop_name,
                   compatibility  as prop_value
          from dba_streams_tp_database) N
    on (T.global_name = N.global_name and T.prop_name = N.prop_name)
    when matched then
         update set T.prop_value = N.prop_value
    when not matched then
         insert (T.global_name, T.prop_name, T.prop_value)
         values (N.global_name, N.prop_name, N.prop_value);

    merge into streams$_pa_database_prop T
    using (select global_name              as global_name,
                  'MANAGEMENT_PACK_ACCESS' as prop_name,
                   management_pack_access  as prop_value
          from dba_streams_tp_database) N
    on (T.global_name = N.global_name and T.prop_name = N.prop_name)
    when matched then
         update set T.prop_value = N.prop_value
    when not matched then
         insert (T.global_name, T.prop_name, T.prop_value)
         values (N.global_name, N.prop_name, N.prop_value);
    commit;

    -- check each db_link to get DB_UNIQUE_NAME or ERROR_NUMBER/VERSION.
    CHECK_DATABASE_LINKS();

    -- remove those databases which have never been queried.
    delete from streams$_pa_database_prop
    where global_name in (select global_name
                          from streams$_pa_database
                          where last_queried is null);
    commit;
    delete from streams$_pa_database
    where last_queried is null;
    commit;

    -- upsert streams$_pa_component.
    merge into streams$_pa_component T
    using (select component_id,
                  component_name,
                  component_db,
                  component_type,
                  component_changed_time
           from dba_streams_tp_component) N
    on (T.component_id = N.component_id)
    when matched then
        update set T.component_changed_time = N.component_changed_time
    when not matched then
        insert (T.component_id,
                T.component_name,
                T.component_db,
                T.component_type,
                T.component_changed_time)
        values (N.component_id,
                N.component_name,
                N.component_db,
                N.component_type,
                N.component_changed_time);
    commit;

    -- upsert streams$_pa_component_link.
    merge into streams$_pa_component_link T
    using (select path_id,
                  position,
                  source_component_id,
                  destination_component_id
           from dba_streams_tp_component_link) N
    on (T.path_id = N.path_id and
        T.position = N.position and
        T.source_component_id = N.source_component_id and
        T.destination_component_id = N.destination_component_id)
    when not matched then
        insert (T.path_id,
                T.position,
                T.source_component_id,
                T.destination_component_id)
        values (N.path_id,
                N.position,
                N.source_component_id,
                N.destination_component_id);
    commit;

    -- persist component properties.
    delete from streams$_pa_component_prop;
    commit;
    insert into streams$_pa_component_prop(
           component_id,
           prop_name,
           prop_value)
    select component_id,
           prop_name,
           prop_value
    from "_DBA_STREAMS_TP_COMPONENT_PROP";
    commit;

    -- persist component statistics.
    insert into streams$_pa_component_stat(
           advisor_run_id,
           advisor_run_time,
           component_id,
           sub_component_type,
           session_id,
           session_serial#,
           statistic_time,
           statistic_name,
           statistic_value,
           statistic_unit, 
           spare3)
    select advisor_run_id,
           advisor_run_time,
           component_id,
           sub_component_type,
           session_id,
           session_serial#,
           statistic_time,
           statistic_name,
           -- The state statistic will be a varchar2, store this in spare3
           decode(statistic_name,
                  'STATE', 0,
                  statistic_value),
           statistic_unit,
           -- Spare3 will store varchar2 statistic values
           decode(statistic_name,
                  'STATE', statistic_value,
                  NULL)
    from DBA_STREAMS_TP_COMPONENT_STAT
    where advisor_run_id = PERSIST_ADVISOR_RUN.run_id;
    commit;

    -- persist path statistics.
    insert into streams$_pa_path_stat(
           advisor_run_id,
           advisor_run_time,
           path_id,
           statistic_time,
           statistic_name,
           statistic_value,
           statistic_unit)
    select advisor_run_id,
           advisor_run_time,
           path_id,
           statistic_time,
           statistic_name,
           statistic_value,
           statistic_unit
    from DBA_STREAMS_TP_PATH_STAT
    where advisor_run_id = PERSIST_ADVISOR_RUN.run_id;
    commit;

    -- persist path bottleneck information.
    insert into streams$_pa_path_bottleneck(
           advisor_run_id,
           advisor_run_time,
           advisor_run_reason,
           path_id,
           component_id,
           top_session_id,
           top_session_serial#,
           action_name,
           bottleneck_identified)
    select advisor_run_id,
           advisor_run_time,
           advisor_run_reason,
           path_id,
           component_id,
           top_session_id,
           top_session_serial#,
           action_name,
           bottleneck_identified
    from DBA_STREAMS_TP_PATH_BOTTLENECK
    where advisor_run_id = PERSIST_ADVISOR_RUN.run_id;
    commit;
    
    -- EM requires path_key to uniquely identify a stream path.
    --
    -- path_key: apply_name@apply_database_global_name.
    -- 
    -- Streams performance advisor reports only complete path
    -- starting with CAPTURE and ending with APPLY. The apply
    -- name and database is the unique key for stream path.

    -- update path_key for apply component
    update streams$_pa_component_link L
    set path_key = (
        select distinct (component_name || '@' || component_db)
        from streams$_pa_component C
        where C.component_type in ('APPLY', 'EXTRACT') and 
              C.component_id = L.destination_component_id);
    commit;

    -- update path_key for other components
    update streams$_pa_component_link L
    set path_key = (
        select distinct  L2.path_key
        from streams$_pa_component C,
             streams$_pa_component_link L2
        where L.path_id = L2.path_id and
              C.component_type in ('APPLY', 'EXTRACT') and 
              C.component_id = L2.destination_component_id);
    commit;
    
    -- update path_key for path stat
    update streams$_pa_path_stat T
    set path_key = (
        select distinct L.path_key
        from streams$_pa_component_link L
        where T.path_id = L.path_id)
    where advisor_run_id = PERSIST_ADVISOR_RUN.run_id and
          advisor_run_time = run_time;
    commit;

    -- update path_key for path bottleneck
    update streams$_pa_path_bottleneck T
    set path_key = (
        select distinct L.path_key
        from streams$_pa_component_link L
        where T.path_id = L.path_id)
    where advisor_run_id = PERSIST_ADVISOR_RUN.run_id and
          advisor_run_time = run_time;
    commit;

     -- Update bottleneck output for html report
     open path_id_cur for 'select distinct path_id from ' || 
                          ' DBA_STREAMS_TP_PATH_BOTTLENECK';
     loop
       fetch path_id_cur into path_id;
       exit when path_id_cur%notfound;
       
       bottleneck := get_bottleneck_html(PERSIST_ADVISOR_RUN.run_id, 
                                         path_id);

       update streams$_pa_path_bottleneck B
       set B.spare3 =  bottleneck
       where B.advisor_run_id = PERSIST_ADVISOR_RUN.run_id 
             and B.path_id =  PERSIST_ADVISOR_RUN.path_id;
       commit;
     end loop;
     close path_id_cur;

    -- persist advisor control parameters.
    SAVE_CONTROL_PARAMS(run_id, run_time);

    -- recover monitoring and control params.
    RECOVER_CONTROL_PARAMS();

    -- reload control params in case of altering.
    LOAD_CONTROL_PARAMS();

    -- clean obsolete data based on retention_time.
    CLEAN_RETENTION(run_time);

  end PERSIST_ADVISOR_RUN;

  ----------------------------------------------------------------------------=
  -- PERSIST_ADVISOR_RUNS
  --   Collect statistics for all active stream paths and
  --   save in the persistent tables.
  ----------------------------------------------------------------------------=
  procedure PERSIST_ADVISOR_RUNS(comp_stat_tbl in varchar2,
                                 path_stat_tbl in varchar2)
  as
    prev_time     date;
    curr_time     date;
    sleep_time    number := 0;
    last_run_id   number := 0;
  begin
    -- Get the last advisor_run_id
    begin
      select max(advisor_run_id) into last_run_id
      from dba_streams_tp_component_stat;
  
      if last_run_id is null then
        last_run_id := 0;
      end if;
    end;

    -- Load advisor control parameters before the first advisor run
    LOAD_CONTROL_PARAMS();

    -- Load advisor monitoring information before the first advisor run
    LOAD_MONITORING_INFO();

    -- No exit condition. Monitoring is only stopped by stop_monitoring.
    while(TRUE)
    loop
      prev_time := sysdate();
      dbms_streams_advisor_adm.analyze_current_performance;
      curr_time := sysdate();
  
      last_run_id := last_run_id + 1;
      dbms_output.put_line('Run ' || last_run_id);

      -- Save line-display statistics
      PERSIST_LINE_DISPLAY(last_run_id, comp_stat_tbl, path_stat_tbl);

      -- Save result for each advisor run
      PERSIST_ADVISOR_RUN(last_run_id);

      sleep_time := param_interval - (curr_time - prev_time) * 86400;
      if sleep_time > 0 then
        dbms_lock.sleep(sleep_time);
      end if;
    end loop;

    -- Update monitoring job state in case of auto-drop
    update streams$_pa_monitoring
    set state = 'STOPPED', stopped_time = systimestamp
    where state = 'STARTED';
    commit;

  end PERSIST_ADVISOR_RUNS;

  ----------------------------------------------------------------------------=
  -- ALTER_MONITORING_PARAM
  --   Alter Streams monitoring job control parameter.
  ----------------------------------------------------------------------------=
  procedure ALTER_MONITORING_PARAM(p_name       IN VARCHAR2,
                                   p_value      IN VARCHAR2,
                                   p_unit       IN VARCHAR2,
                                   altered_time IN TIMESTAMP DEFAULT NULL)
  as
  begin

    merge into streams$_pa_control T
    using (select p_value as param_value from dual) N
    on (T.advisor_run_id = 0 and
        T.advisor_run_time is null and
        T.param_name = p_name)
    when matched then
        update set T.param_value = N.param_value
    when not matched then
        insert (T.advisor_run_id,
                T.advisor_run_time,
                T.param_name,
                T.param_value,
                T.param_unit)
        values (0, null, p_name, p_value, p_unit);
    commit;

    if (altered_time is not null) then
      update streams$_pa_monitoring T
      set T.altered_time = ALTER_MONITORING_PARAM.altered_time
      where T.state = 'STARTED';
      commit;
    end if;

  end ALTER_MONITORING_PARAM;

  ----------------------------------------------------------------------------=
  -- CHECK_JOB_CONFLICT
  --   EM agent must have client_name set to to 'EM'.
  --   No more than one active monitoring job exists at any time.
  --
  -- RAISED EXCEPTIONS:
  --
  -- ORA-20111:
  --   cannot start monitoring due to active EM monitoring job
  -- ORA-20112:
  --   cannot start monitoring due to active Streams monitoring job
  --
  ----------------------------------------------------------------------------=
  procedure CHECK_JOB_CONFLICT(canon_job_name    varchar2,
                               canon_client_name varchar2)
  as
    sch_job_cnt number := 0;
    monitoring boolean := FALSE;
    active_cnt number := 0;
    active_em_cnt number := 0;
    canon_submit_job_name varchar(258) := null;
  begin
    select count(*) into active_cnt
    from streams$_pa_monitoring
    where state = 'STARTED';

    if (active_cnt = 0) then
      -- Check if there is an EM monitoring job per database.
      if ((canon_client_name is not null)
           and (canon_client_name = 'EM')
           and IS_MONITORING(canon_job_name, canon_client_name)) then
        raise_application_error(-20111,
          'cannot start monitoring ' ||
          'due to active EM monitoring job');
      end if;
    else
      if canon_client_name is not null then
         canon_submit_job_name := canon_job_name ||'_'|| canon_client_name;
      else
         canon_submit_job_name := canon_job_name;
      end if;

      begin
        select count(*) into sch_job_cnt from dba_scheduler_jobs
        where enabled = 'TRUE' and
              job_name = canon_submit_job_name;
      exception when others then
        sch_job_cnt := 0;
      end;

      if (sch_job_cnt = 0) then
        -- no scheduled job is found, database may be bounced.
        update streams$_pa_monitoring set state = 'STOPPED'
        where job_name = canon_job_name and
              client_name||'X' = canon_client_name||'X';
        commit;
      else
        select count(*) into active_em_cnt
        from streams$_pa_monitoring
        where state = 'STARTED' and client_name = 'EM';

        if (active_em_cnt > 0) then
          raise_application_error(-20111,
            'cannot start monitoring '||
            'due to active EM monitoring job');
        else
          raise_application_error(-20112,
            'cannot start monitoring '||
            'due to active Streams monitoring job');
        end if;
      end if;
    end if;

  end CHECK_JOB_CONFLICT;

  ----------------------------------------------------------------------------=
  -- COLLECT_STATS
  --   Collect statistics for all active stream paths.
  ----------------------------------------------------------------------------=
  procedure COLLECT_STATS(
    interval                  in number   default 60,
    num_runs                  in number   default 10,
    comp_stat_table           in varchar2 default 'STREAMS$_ADVISOR_COMP_STAT',
    path_stat_table           in varchar2 default 'STREAMS$_ADVISOR_PATH_STAT',
    top_event_threshold       in number   default 15,
    bottleneck_idle_threshold in number   default 50,
    bottleneck_flowctrl_threshold in number default 50)
  as
    prev_time     date;
    curr_time     date;
    sleep_time    number := 0;
    last_run_id   number := 0;
    comp_stat_tbl varchar2(30);
    path_stat_tbl varchar2(30);
    param_setting varchar2(2000);

    cnt           number := 0;
    monitoring    boolean:= FALSE;
    job_started   boolean:= FALSE;
  begin
    -- Initialize the tables if they do not exist 
    comp_stat_tbl := INIT_SPADV_COMP_STAT(comp_stat_table);
    path_stat_tbl := INIT_SPADV_PATH_STAT(path_stat_table);

    -- Check if invoked by START_MONITORING
    begin
      select count(*) into cnt
      from streams$_pa_monitoring
      where state = 'STARTED';

      if cnt > 0 then
        job_started := TRUE;
      end if;
    exception when others then
      job_started := FALSE;
    end;

    -- Check control parameters
    if (nvl(interval, 60) < 1) then
      raise_application_error(-20100, 'Invalid interval, too small');
    end if;

    if (nvl(num_runs, 10) < 1) then
      raise_application_error(-20100, 'Invalid num_runs, too small');
    end if;

    if (nvl(top_event_threshold, 15) < 0 or
        nvl(top_event_threshold, 15) > 100 ) then
      raise_application_error(-20100, 'Invalid top_event_threshold');
    end if;
  
    if (nvl(bottleneck_idle_threshold, 50) < 0 or
        nvl(bottleneck_idle_threshold, 50) > 100 ) then
      raise_application_error(-20100, 'Invalid bottleneck_idle_threshold');
    end if;
  
    if (nvl(bottleneck_flowctrl_threshold, 50) < 0 or
        nvl(bottleneck_flowctrl_threshold, 50) > 100 ) then
      raise_application_error(-20100, 'Invalid bottleneck_flowctrl_threshold');
    end if;
  
    param_interval               := nvl(interval, 60); -- in seconds
    param_top_event_threshold    := nvl(top_event_threshold, 15);
    param_bot_idle_threshold     := nvl(bottleneck_idle_threshold, 50);
    param_bot_flowctrl_threshold := nvl(bottleneck_flowctrl_threshold, 50);

    -- Set bottleneck_idle_threshold and bottleneck_flowctrl_threshold
    dbms_streams_advisor_adm.set_bottleneck_idle_threshold(
      param_bot_idle_threshold);
    dbms_streams_advisor_adm.set_bottleneck_flowctrl_thresh(
      param_bot_flowctrl_threshold);

    -- check if invoked by start_monitoring.
    if job_started = TRUE then
      if(comp_stat_tbl <> 'STREAMS$_PA_SHOW_COMP_STAT' or
         path_stat_tbl <> 'STREAMS$_PA_SHOW_PATH_STAT' ) then
        monitoring := FALSE;
      else
        monitoring := TRUE;
      end if;
    else
      monitoring := FALSE;
    end if;

    --
    -- START_MONITORING
    --
    if monitoring = TRUE then
      PERSIST_ADVISOR_RUNS(comp_stat_tbl, path_stat_tbl);
      return;
    end if;

    if (monitoring = FALSE and num_runs is null) then
      raise_application_error(-20100, 'Invalid num_runs, '||
                                      'cannot be NULL without monitoring');
    end if;

    --
    -- COLLECT_STATS (blocking-mode)
    --

    -- Get the last advisor_run_id
    begin
      select max(advisor_run_id) into last_run_id
      from dba_streams_tp_component_stat;
  
      if last_run_id is null then
        last_run_id := 0;
      end if;
    end;
  
    for i in 1 .. nvl(num_runs, 10) loop
      prev_time := sysdate();
      dbms_streams_advisor_adm.analyze_current_performance;
      curr_time := sysdate();
  
      -- Save result for each run
      last_run_id := last_run_id + 1;
      dbms_output.put_line('Run ' || last_run_id);

      PERSIST_LINE_DISPLAY(last_run_id,
                           comp_stat_tbl,
                           path_stat_tbl,
                           TRUE, TRUE);
  
      -- No sleep for the last run
      if (i < num_runs) then
        sleep_time := interval - (curr_time - prev_time) * 86400;
        if sleep_time > 0 then
          dbms_lock.sleep(sleep_time);
        end if;
      end if;
    end loop;
  end COLLECT_STATS;

  ----------------------------------------------------------------------------=
  -- SHOW_STATS
  --   Print statistics for a stream path.
  ----------------------------------------------------------------------------=
  procedure SHOW_STATS(
    path_stat_table   in varchar2 default 'STREAMS$_ADVISOR_PATH_STAT',
    path_id           in number   default null,  -- show all stream paths
    bgn_run_id        in number   default -1,    -- show the last 10 runs
    end_run_id        in number   default -10,
    show_path_id      in boolean  default TRUE,
    show_run_id       in boolean  default TRUE,
    show_run_time     in boolean  default TRUE,
    show_optimization in boolean  default TRUE,
    show_setting      in boolean  default FALSE,
    show_stat         in boolean  default TRUE,
    show_sess         in boolean  default FALSE,
    show_legend       in boolean  default TRUE
  ) as
    type cur_type is ref cursor;
    type stat_rec_type is record(
              path_id            number,
              advisor_run_id     number,
              advisor_run_time   date,
              setting            varchar2(200),
              statistics         varchar2(4000),
              session_statistics varchar2(4000),
              optimization       number);
    cur       cur_type;
    stat_rec  stat_rec_type;
    svalue    varchar2(2000);
    stmt      varchar2(2000);
    stmt_run  varchar2(2000);
    chk_table varchar2(30);
  begin

    if (upper(path_stat_table) = 'STREAMS$_ADVISOR_PATH_STAT') then
       -- Initialize the table if the default table does not exist.
       chk_table := INIT_SPADV_PATH_STAT(path_stat_table);
    else
       -- Check if the user specified path stat table exists.
       chk_table := CHECK_SPADV_PATH_STAT(path_stat_table);
    end if;

    -- Print the legend
    if show_legend = TRUE then 
      dbms_output.put_line('LEGEND');
      dbms_output.put_line(
        '<statistics>= <capture>|<replicat> [ <queue> <psender> <preceiver> <queue> ] '||
        '<apply>|<extract> <bottleneck>');
  
      dbms_output.put_line(
        '<capture>   = ''|<C>'' <name> <msgs captured/sec> '||
        '<msgs enqueued/sec> <latency>');
      dbms_output.put_line(chr(9)||'            '||
        '''LMR'' <idl%> <flwctrl%> <topevt%> <topevt>');
      dbms_output.put_line(chr(9)||'            '||
        '''LMP'' (<parallelism>) <idl%> <flwctrl%> <topevt%> <topevt>');
      dbms_output.put_line(chr(9)||'            '||
        '''LMB'' <idl%> <flwctrl%> <topevt%> <topevt>');
      dbms_output.put_line(chr(9)||'            '||
        '''CAP'' <idl%> <flwctrl%> <topevt%> <topevt>');
      dbms_output.put_line(chr(9)||'            '||
        '''CAP+PS'' <msgs sent/sec> <bytes sent/sec> ' ||
        '<latency> <idl%> <flwctrl%> <topevt%> <topevt>');
  
      dbms_output.put_line(
        '<apply>     = ''|<A>'' <name> <msgs applied/sec> ' ||
        '<txns applied/sec> <latency>');
      dbms_output.put_line(chr(9)||'            '||
        '''PS+PR'' <idl%> <flwctrl%> <topevt%> <topevt>');
      dbms_output.put_line(chr(9)||'            '||
        '''APR'' <idl%> <flwctrl%> <topevt%> <topevt>');
      dbms_output.put_line(chr(9)||'            '||
        '''APC'' <idl%> <flwctrl%> <topevt%> <topevt>');
      dbms_output.put_line(chr(9)||'            '||
        '''APS'' (<parallelism>) <idl%> <flwctrl%> <topevt%> <topevt>');
     
      dbms_output.put_line(
        '<extract>   = ''|<E>'' <name> <msgs sent/sec> ' ||
        '<bytes sent/sec> <latency> <idl%> <flwctrl%> <topevt%> <topevt>');
      dbms_output.put_line(
        '<replicat>  = ''|<R>'' <name> <msgs recd/sec> '||
        '<bytes recd/sec> <idl%> <flwctrl%> <topevt%> <topevt>');
      dbms_output.put_line(
        '<queue>     = ''|<Q>'' <name> <msgs enqueued/sec> '||
        '<msgs spilled/sec> <msgs in queue>');
      dbms_output.put_line(
        '<psender>   = ''|<PS>'' <name> <msgs sent/sec> <bytes sent/sec> '||
        '<latency> <idl%> <flwctrl%> <topevt%> <topevt>');
      dbms_output.put_line(
        '<preceiver> = ''|<PR>'' <name> <idl%> <flwctrl%> <topevt%> <topevt>');
      dbms_output.put_line(
        '<bottleneck>= ''|<B>'' <name> <sub_name> '||
        '<sessionid> <serial#> <topevt%> <topevt>');
      dbms_output.put_line(chr(10));
      dbms_output.put_line('OUTPUT');
    end if;
  
    -- Check arguments
    if (path_id is not null and path_id < 1) then
      raise_application_error(-20100, 'Invalid path_id');
    end if;
    if (bgn_run_id is null or bgn_run_id = 0) then
      raise_application_error(-20100, 'Invalid bgn_run_id');
    end if;
    if (end_run_id is null or end_run_id = 0) then
      raise_application_error(-20100, 'Invalid end_run_id');
    end if;
  
    -- Start with the first run (1)
    if (bgn_run_id > 0 and bgn_run_id > end_run_id) then
      raise_application_error(-20100, 'Invalid end_run_id');
    end if;
    -- Start with the latest run (-1)
    if (bgn_run_id < 0 and bgn_run_id < end_run_id ) then
      raise_application_error(-20100, 'Invalid end_run_id');
    end if;
  
    stmt_run :=
      case when bgn_run_id > 0 then
            '(select ' || bgn_run_id || ' as bgn_run_id, '
                       || end_run_id || ' as end_run_id from dual)'
           else 
            '(select distinct ' ||
            '  (latest_run_id + ' || end_run_id || ' + 1) as bgn_run_id, ' ||
            '  (latest_run_id + ' || bgn_run_id || ' + 1) as end_run_id  ' ||
            ' from ( select advisor_run_id as latest_run_id ' ||
                   ' from ' || upper(chk_table) ||
                   ' where advisor_run_time in (' ||
                          ' select max(advisor_run_time) ' ||
                          ' from ' || upper(chk_table) || ') ) )'
      end;
  
    stmt :=
      'select S.path_id, S.advisor_run_id, S.advisor_run_time, ' ||
      '       S.setting, S.statistics, S.session_statistics, S.optimization '||
      'from ' || upper(chk_table) || ' S, ' || stmt_run || ' R, ' ||
      '     ( select distinct path_id ' ||
      '       from dba_streams_tp_component_link ) P ' ||
      'where S.advisor_run_id >= R.bgn_run_id ' ||
      '  and S.advisor_run_id <= R.end_run_id ' ||
      '  and S.path_id = P.path_id ' ||
      case when path_id is NULL then NULL
           else ' and S.path_id  = ' || path_id
      end ||
      ' order by P.path_id, S.advisor_run_time, S.advisor_run_id';
  
    begin
      open cur for stmt;
      loop
        fetch cur into stat_rec;
        exit when cur%notfound;
  
        -- Print PATH RUN_ID RUN_TIME
        -- PATH 2  RUN_ID 8  RUN_TIME 29-MAY-007 15:58:04
        svalue := null;
        if show_path_id = TRUE then
          svalue := 'PATH ' || stat_rec.path_id;
        end if;
  
        if show_run_id = TRUE then
          svalue := case when svalue is null then null
                         else svalue || ' ' end ||
                    'RUN_ID ' || stat_rec.advisor_run_id;
        end if;
  
        if show_run_time = TRUE then
          svalue := case when svalue is null then null
                         else svalue || ' ' end ||
                    'RUN_TIME ' ||
                 to_char(stat_rec.advisor_run_time, 'YYYY-MON-DD HH24:MI:SS');
        end if;

        if show_optimization = TRUE then
          svalue := case when svalue is null then null
                         else svalue || ' ' end ||
                    'CCA ' ||
                    case when stat_rec.optimization = 0
                         then 'N'
                         else 'Y'
                    end;
        end if;
  
        if svalue is not null then
          dbms_output.put_line(svalue);
        end if;
  
        -- Print SETTING
        if show_setting = TRUE then
          dbms_output.put_line(stat_rec.setting);
        end if;
  
        -- Print STATISTICS
        if show_stat = TRUE then
          dbms_output.put_line(stat_rec.statistics);
        end if;
  
        -- Print SESSION_STATISTICS
        if show_sess = TRUE then
          dbms_output.put_line(stat_rec.session_statistics);
        end if;
  
        dbms_output.put_line(chr(10));
      end loop;
      close cur;
    exception when others then
      if cur%isopen then
        close cur;
      end if;
      raise;
    end;
  
  end SHOW_STATS;

  ----------------------------------------------------------------------------=
  -- GET_ACTIVE_JOB_NAME
  --   Gets the scheduled job name;
  ----------------------------------------------------------------------------=
  function GET_ACTIVE_JOB_NAME return varchar2
  as
    active_job_name   dbms_id := null;
  begin
    begin
      select case when client_name is null then job_name
                  else job_name || '_' || client_name
             end into active_job_name
      from streams$_pa_monitoring
      where state = 'STARTED';
    exception when others then
      active_job_name := null;
    end;

    return active_job_name;
  end GET_ACTIVE_JOB_NAME;

  ----------------------------------------------------------------------------=
  -- GET_LATEST_JOB_NAME
  --   Gets the most recently scheduled job name;
  ----------------------------------------------------------------------------=
  function GET_LATEST_JOB_NAME return varchar2
  as
    latest_job_name   dbms_id := null;
  begin
    -- don't care if STARTED or not
    begin
      select job_name into latest_job_name
      from ( select case when client_name is null then job_name
                         else job_name || '_' || client_name
                    end as job_name 
             from streams$_pa_monitoring
             order by started_time desc )
      where rownum = 1;
    exception when others then
      latest_job_name := null;
    end;

    return latest_job_name;
  end GET_LATEST_JOB_NAME;

  ----------------------------------------------------------------------------=
  -- IS_MONITORING
  --   Checks if a client has submitted a monitoring job.
  ----------------------------------------------------------------------------=
  function IS_MONITORING(
    job_name    IN VARCHAR2 DEFAULT 'STREAMS$_MONITORING_JOB',
    client_name IN VARCHAR2 DEFAULT NULL) return BOOLEAN
  as
    ret                   NUMBER := 0;
    tbl                   VARCHAR2(30) := null;
    canon_job_name        dbms_id      := null;
    canon_client_name     dbms_id      := null;
    canon_submit_job_name dbms_id      := null;
  begin
    -- check table
    tbl := INIT_TBL_PA_MONITORING();

    if job_name is null then
      canon_job_name := 'STREAMS$_MONITORING_JOB';
    else
      dbms_utility.canonicalize(job_name, canon_job_name, 128);
    end if;

    if client_name is not null then
      dbms_utility.canonicalize(client_name, canon_client_name, 128);
    end if;

    if canon_client_name is not null then
       if not ((length(canon_job_name) + length(canon_client_name)) < 128) then
         raise_application_error(-20100,
           'Combined length of job_name and client_name must be less than 128');
       end if;

       dbms_utility.canonicalize(
         canon_job_name ||'_'|| canon_client_name, canon_submit_job_name, 128);
    else
       canon_submit_job_name := canon_job_name;
    end if;

    -- At most one EM monitoring job per database.
    if (canon_client_name is not null and canon_client_name = 'EM') then
      begin
        select 1 into ret from dba_scheduler_jobs
        where enabled = 'TRUE' and
              job_name = canon_submit_job_name;
      exception when others then
        ret := 0;
      end;
    else
    -- At most one monitoring job per schema.
      begin
        select 1 into ret from streams$_pa_monitoring
        where state = 'STARTED' and
              job_name = canon_job_name and
              client_name||'X' = canon_client_name||'X';
      exception when others then
        ret := 0;
      end;

    end if;

    if ret = 1 then
      return TRUE;
    else
      return FALSE;
    end if;

  end IS_MONITORING;

  ----------------------------------------------------------------------------=
  -- GRANT_PRIVILEGE
  --   Grant select privilege to query user.
  ----------------------------------------------------------------------------=
  procedure GRANT_PRIVILEGE(canon_query_user in VARCHAR2)
  as
    stmt varchar2(400) := null;
    head varchar2(100) := null;
    tail varchar2(132) := null; -- canon_query_user(128) + 4
  begin
    head := 'GRANT SELECT ON ';
    tail := ' TO ' || canon_query_user;

    stmt := head || 'STREAMS$_PA_MONITORING' || tail;
    execute immediate stmt;
    
    stmt := head || 'STREAMS$_PA_DATABASE' || tail;
    execute immediate stmt;
    
    stmt := head || 'STREAMS$_PA_DATABASE_PROP' || tail;
    execute immediate stmt;
    
    stmt := head || 'STREAMS$_PA_CONTROL' || tail;
    execute immediate stmt;
    
    stmt := head || 'STREAMS$_PA_COMPONENT' || tail;
    execute immediate stmt;
    
    stmt := head || 'STREAMS$_PA_COMPONENT_PROP' || tail;
    execute immediate stmt;
    
    stmt := head || 'STREAMS$_PA_COMPONENT_LINK' || tail;
    execute immediate stmt;
    
    stmt := head || 'STREAMS$_PA_COMPONENT_STAT' || tail;
    execute immediate stmt;
    
    stmt := head || 'STREAMS$_PA_PATH_STAT' || tail;
    execute immediate stmt;
    
    stmt := head || 'STREAMS$_PA_PATH_BOTTLENECK' || tail;
    execute immediate stmt;
    
    stmt := head || 'STREAMS$_PA_SHOW_COMP_STAT' || tail;
    execute immediate stmt;
    
    stmt := head || 'STREAMS$_PA_SHOW_PATH_STAT' || tail;
    execute immediate stmt;
    
  end GRANT_PRIVILEGE;
  
  ----------------------------------------------------------------------------=
  -- START_MONITORING
  -- 
  -- Raised Exceptions:
  --   ORA-20100:
  --     'Invalid interval, too small'
  --     'Invalid top_event_threshold'
  --     'Invalid bottleneck_idle_threshold'
  --     'Invalid bottleneck_flowctrl_threshold'
  --     'Invalid retention_time, too small'
  --     'Combined length of job_name and client_name must be less than 30'
  --
  ----------------------------------------------------------------------------=
  procedure START_MONITORING(
    job_name                     IN VARCHAR2 DEFAULT 'STREAMS$_MONITORING_JOB',
    client_name                  IN VARCHAR2 DEFAULT NULL,
    query_user_name              IN VARCHAR2 DEFAULT NULL,
    interval                     IN NUMBER DEFAULT 60,
    top_event_threshold          IN NUMBER DEFAULT 15,
    bottleneck_idle_threshold    IN NUMBER DEFAULT 50,
    bottleneck_flowctrl_threshold IN NUMBER DEFAULT 50,
    retention_time                IN NUMBER DEFAULT 24)
  as
    tbl                   VARCHAR2(30) := null;
    canon_job_name        dbms_id      := null;
    canon_client_name     dbms_id      := null;
    canon_query_user_name dbms_id      := null;
    canon_submit_job_name dbms_id      := null;
    start_monitoring_time timestamp := systimestamp;
  begin
    -- Check tables
    tbl := INIT_TBL_PA_MONITORING();
    tbl := INIT_TBL_PA_DATABASE();
    tbl := INIT_TBL_PA_DATABASE_PROP();
    tbl := INIT_TBL_PA_COMPONENT();
    tbl := INIT_TBL_PA_COMPONENT_LINK();
    tbl := INIT_TBL_PA_COMPONENT_PROP();
    tbl := INIT_TBL_PA_CONTROL();
    tbl := INIT_TBL_PA_COMPONENT_STAT();
    tbl := INIT_TBL_PA_PATH_STAT();
    tbl := INIT_TBL_PA_PATH_BOTTLENECK();
    tbl := INIT_TBL_PA_SHOW_COMP_STAT();
    tbl := INIT_TBL_PA_SHOW_PATH_STAT();

    if job_name is null then
      canon_job_name := 'STREAMS$_MONITORING_JOB';
    else
      dbms_utility.canonicalize(job_name, canon_job_name, 128);
    end if;

    if client_name is not null then
      dbms_utility.canonicalize(client_name, canon_client_name, 128);
    end if;

    if query_user_name is not null then
      dbms_utility.canonicalize(query_user_name, canon_query_user_name, 128);
      GRANT_PRIVILEGE(canon_query_user_name);
    end if;

    if canon_client_name is not null then
       if not ((length(canon_job_name) + length(canon_client_name)) < 128) then
         raise_application_error(-20100,
           'Combined length of job_name and client_name must be less than 128');
       end if;
       dbms_utility.canonicalize(
         canon_job_name ||'_'|| canon_client_name, canon_submit_job_name, 128);
    else
       canon_submit_job_name := canon_job_name;
    end if;

    CHECK_JOB_CONFLICT(canon_job_name, canon_client_name);

    -- Check control parameters
    if (nvl(interval, 60) < 1) then
      raise_application_error(-20100, 'Invalid interval, too small');
    end if;

    if (nvl(top_event_threshold, 15) < 0 or
        nvl(top_event_threshold, 15) > 100 ) then
      raise_application_error(-20100, 'Invalid top_event_threshold');
    end if;
  
    if (nvl(bottleneck_idle_threshold, 50) < 0 or
        nvl(bottleneck_idle_threshold, 50) > 100 ) then
      raise_application_error(-20100, 'Invalid bottleneck_idle_threshold');
    end if;
  
    if (nvl(bottleneck_flowctrl_threshold, 50) < 0 or
        nvl(bottleneck_flowctrl_threshold, 50) > 100 ) then
      raise_application_error(-20100, 'Invalid bottleneck_flowctrl_threshold');
    end if;

    -- Retention time must at least cover 10 intervals
    if (nvl(retention_time, 24) <
        (nvl(interval, 60) * 10)/seconds_per_hour) then
      raise_application_error(-20100, 'Invalid retention_time, too small');
    end if;

    param_interval               := nvl(interval, 60); -- in seconds
    param_retention_time         := nvl(retention_time, 24); -- in hours
    param_top_event_threshold    := nvl(top_event_threshold, 15);
    param_bot_idle_threshold     := nvl(bottleneck_idle_threshold, 50);
    param_bot_flowctrl_threshold := nvl(bottleneck_flowctrl_threshold, 50);

    -- Print trace info
    dbms_output.put_line('Monitoring Job Control Parameters:');
    dbms_output.put_line(chr(9)||'  INTERVAL='||
      param_interval);
    dbms_output.put_line(chr(9)||'  RETENTION_TIME='||
      param_retention_time);
    dbms_output.put_line(chr(9)||'  TOP_EVENT_THRESHOLD='||
      param_top_event_threshold);
    dbms_output.put_line(chr(9)||'  BOTTLENECK_IDLE_THRESHOLD='||
      param_bot_idle_threshold);
    dbms_output.put_line(chr(9)||'  BOTTLENECK_FLOWCTRL_THRESHOLD='||
      param_bot_flowctrl_threshold);

    -- Start the monitoring job in 1 second. 
    start_monitoring_time := systimestamp + (1/seconds_per_day);

    insert into streams$_pa_monitoring(
           job_name,
           client_name,
           query_user_name,
           started_time,
           stopped_time,
           altered_time,
           state)
    values(canon_job_name,
           canon_client_name,
           canon_query_user_name,
           start_monitoring_time,
           null,
           null,
           'STARTED');
    commit;

    alter_monitoring_param('INTERVAL',
                           param_interval,
                           'SECOND');
    alter_monitoring_param('RETENTION_TIME',
                           param_retention_time,
                           'HOUR');
    alter_monitoring_param('TOP_EVENT_THRESHOLD',
                           param_top_event_threshold,
                           'PERCENT');
    alter_monitoring_param('BOTTLENECK_IDLE_THRESHOLD',
                           param_bot_idle_threshold,
                           'PERCENT');
    alter_monitoring_param('BOTTLENECK_FLOWCTRL_THRESHOLD',
                           param_bot_flowctrl_threshold,
                           'PERCENT');

    begin
      dbms_scheduler.create_job(
        job_name    => canon_submit_job_name, 
        start_date  => start_monitoring_time,
        repeat_interval => null,
        job_type    => 'plsql_block',
        job_action  => 'begin utl_spadv.collect_stats(' ||
                       'interval => ' || param_interval || ', ' ||
                       'num_runs => NULL, ' ||
                       'comp_stat_table => ''streams$_pa_show_comp_stat'', ' ||
                       'path_stat_table => ''streams$_pa_show_path_stat'', ' ||
                       'top_event_threshold => ' ||
                          param_top_event_threshold || ', ' ||
                       'bottleneck_idle_threshold => ' ||
                          param_bot_idle_threshold || ', ' ||
                       'bottleneck_flowctrl_threshold => ' ||
                          param_bot_flowctrl_threshold ||
                       '); end;',
        number_of_arguments => 0,
        enabled     => TRUE,
        auto_drop   => FALSE);

      dbms_scheduler.set_attribute(
        name      =>  canon_submit_job_name,
        attribute => 'restartable',
        value     => true);
    exception when others then
      delete from streams$_pa_monitoring
      where state = 'STARTED' and
            job_name = canon_job_name and
            started_time = start_monitoring_time;
      commit;

      raise;
    end;

  end START_MONITORING;

  ----------------------------------------------------------------------------=
  -- ALTER_MONITORING
  -- 
  -- Raised Exceptions:
  --   ORA-20113: 'no active monitoring job found'
  ----------------------------------------------------------------------------=
  procedure ALTER_MONITORING(
    interval                      IN NUMBER DEFAULT null,
    top_event_threshold           IN NUMBER DEFAULT null,
    bottleneck_idle_threshold     IN NUMBER DEFAULT null,
    bottleneck_flowctrl_threshold IN NUMBER DEFAULT null,
    retention_time                IN NUMBER DEFAULT null)
  as
    tbl             VARCHAR2(30) := null;
    p_name          dbms_id      := null;
    p_value         NUMBER       := null;
    p_unit          dbms_id      := null;
    active_job_name dbms_id      := null;
  begin
    -- Check tables
    tbl := INIT_TBL_PA_CONTROL();
    tbl := INIT_TBL_PA_MONITORING();

    active_job_name := GET_ACTIVE_JOB_NAME();
    if(active_job_name is null) then
      raise_application_error(-20113, 'no active monitoring job found');
    end if;

    -- Check control parameters
    if (nvl(interval, 60) < 1) then
      raise_application_error(-20100, 'Invalid interval, too small');
    end if;

    if (nvl(top_event_threshold, 15) < 0 or
        nvl(top_event_threshold, 15) > 100 ) then
      raise_application_error(-20100, 'Invalid top_event_threshold');
    end if;
  
    if (nvl(bottleneck_idle_threshold, 50) < 0 or
        nvl(bottleneck_idle_threshold, 50) > 100 ) then
      raise_application_error(-20100, 'Invalid bottleneck_idle_threshold');
    end if;
  
    if (nvl(bottleneck_flowctrl_threshold, 50) < 0 or
        nvl(bottleneck_flowctrl_threshold, 50) > 100 ) then
      raise_application_error(-20100, 'Invalid bottleneck_flowctrl_threshold');
    end if;

    -- Retention time must at least cover 10 intervals
    if (nvl(retention_time, 24) <
        (nvl(interval, 60) * 10)/seconds_per_hour) then
      raise_application_error(-20100, 'Invalid retention_time, too small');
    end if;

    -- Indicate that job was altered
    update streams$_pa_monitoring
    set altered_time = systimestamp
    where state = 'STARTED';
    commit;

    -- If parameter is null, the original parameter will be kept.
    if (interval is not null) then
      p_name := 'INTERVAL';
      p_value := interval;
      p_unit := 'SECOND';
      alter_monitoring_param(p_name, p_value, p_unit);
    end if;

    if (retention_time is not null) then
      p_name := 'RETENTION_TIME';
      p_value := retention_time;
      p_unit := 'HOUR';
      alter_monitoring_param(p_name, p_value, p_unit);
    end if;

    if (top_event_threshold is not null) then
      p_name := 'TOP_EVENT_THRESHOLD';
      p_value := top_event_threshold;
      p_unit := 'PERCENT';
      alter_monitoring_param(p_name, p_value, p_unit);
    end if;

    if (bottleneck_idle_threshold is not null) then
      p_name := 'BOTTLENECK_IDLE_THRESHOLD';
      p_value := bottleneck_idle_threshold;
      p_unit := 'PERCENT';
      alter_monitoring_param(p_name, p_value, p_unit);
    end if;

    if (bottleneck_flowctrl_threshold is not null) then
      p_name := 'BOTTLENECK_FLOWCTRL_THRESHOLD';
      p_value := bottleneck_flowctrl_threshold;
      p_unit := 'PERCENT';
      alter_monitoring_param(p_name, p_value, p_unit);
    end if;
    
  end ALTER_MONITORING;

  ----------------------------------------------------------------------------=
  -- STOP_MONITORING
  --   Stops persistent monitoring of Streams performance.
  --
  -- Parameters:
  --   purge:      Whether or not to purge monitoring results from disk
  --
  -- Returns:
  --   TRUE if monitoring has been enabled, false otherwise
  --
  -- Raised Exceptions:
  --   ORA-20113: 'no active monitoring job found'
  --
  procedure STOP_MONITORING(purge IN BOOLEAN DEFAULT FALSE)
  as
    tbl             VARCHAR2(30) := null;
    active_job_name dbms_id      := null;
    jobs	          NUMBER       := 0;
  begin
    -- Check tables
    tbl := INIT_TBL_PA_MONITORING();
    tbl := INIT_TBL_PA_DATABASE();
    tbl := INIT_TBL_PA_DATABASE_PROP();
    tbl := INIT_TBL_PA_COMPONENT();
    tbl := INIT_TBL_PA_COMPONENT_LINK();
    tbl := INIT_TBL_PA_COMPONENT_PROP();
    tbl := INIT_TBL_PA_CONTROL();
    tbl := INIT_TBL_PA_COMPONENT_STAT();
    tbl := INIT_TBL_PA_PATH_STAT();
    tbl := INIT_TBL_PA_PATH_BOTTLENECK();
    tbl := INIT_TBL_PA_SHOW_COMP_STAT();
    tbl := INIT_TBL_PA_SHOW_PATH_STAT();

    active_job_name := GET_ACTIVE_JOB_NAME();
    if(active_job_name is null AND purge) then
       active_job_name := GET_LATEST_JOB_NAME();
    end if;

    if(active_job_name is null) then
     raise_application_error(-20113, 'no active monitoring job found');
    end if;

    select count(job_name) into jobs
      from user_scheduler_jobs where job_name = active_job_name;

    if (jobs > 0) then
      dbms_scheduler.drop_job(
        job_name => active_job_name, force => true);
    end if;

    update streams$_pa_monitoring
    set state = 'STOPPED', stopped_time = systimestamp
    where state = 'STARTED';
    commit;

    if purge then
      delete from streams$_pa_control;
      delete from streams$_pa_database;
      delete from streams$_pa_database_prop;
      delete from streams$_pa_component;
      delete from streams$_pa_monitoring;
      delete from streams$_pa_component_link;
      delete from streams$_pa_component_prop;
      delete from streams$_pa_component_stat;
      delete from streams$_pa_path_bottleneck;
      delete from streams$_pa_path_stat;
      delete from streams$_pa_show_comp_stat;
      delete from streams$_pa_show_path_stat;
      commit;
    end if;

  end STOP_MONITORING;

  -----------------------------------------------------------------------------=
  -- HTML REPORT 
  -----------------------------------------------------------------------------=

  -----------------------------------------------------------------------------=
  -- PRINT_HEADER
  --   Prints the headers etc for the html file
  -----------------------------------------------------------------------------=
  procedure PRINT_HEADER(
    directory         in varchar2,
    reportName        in varchar2
  ) IS
    file  utl_file.file_type;
  begin
    
    file := utl_file.fopen(directory, reportName, 'W');
    -- print the headers etc
    utl_file.put_line(file, '<html>');
    utl_file.put_line(file, '<head>');

    -- TODO clean this up
    utl_file.put_line(file, '<script type="text/javascript">'||
         ' function DoNav(theUrl){ ' ||
         ' document.location.href = theUrl;}' || 
         ' function ChangeColor(tableRow, highLight)' ||
         ' { ' ||
         ' if (highLight)' ||
         ' { tableRow.style.backgroundColor = ''red''; }' ||
         ' else { tableRow.style.backgroundColor = ''white''; } }' ||
         ' </script>');

    utl_file.put_line(file, '<style type=''text/css''>' ||
          ' body {  ' ||
          'font:10pt Arial,Helvetica,sans-serif; color:black;' || 
          ' background:White;}' || 
          'p {font:10pt Arial,Helvetica,sans-serif; color:black;' ||
          ' background:White;} ' || 
          'table,tr,td {font:10pt Arial,Helvetica,sans-serif; ' ||
          'color:Black; background:#f7f7e7; padding:0px 0px 0px 0px; ' ||
          'margin:0px 0px 0px 0px;}' ||
          ' th {font:bold 10pt Arial,Helvetica,sans-serif; color:#336699;' ||
          ' background:#cccc99; padding:0px 0px 0px 0px;} ' ||
          ' h1 {font:16pt Arial,Helvetica,Geneva,sans-serif; ' ||
          'color:#336699; background-color:White; border-bottom:1px ' ||
          'solid #cccc99; margin-top:0pt; margin-bottom:0pt; ' ||
          'padding:0px 0px 0px 0px;} h2 {font:bold 10pt Arial,' ||
          'Helvetica,Geneva,sans-serif; color:#336699; ' ||
          'background-color:White; margin-top:4pt; margin-bottom:0pt;} ' || 
          '</style>');
    utl_file.put_line(file, '<title> SPADV Report </title>');
    utl_file.put_line(file, '</head>');
    utl_file.put_line(file, '<body>');

    utl_file.fclose(file);

  end PRINT_HEADER;

  -----------------------------------------------------------------------------=
  -- PRINT_FOOTER
  --   Prints the footer for the html file
  -----------------------------------------------------------------------------=
  procedure PRINT_FOOTER(
    directory         in varchar2,
    reportName        in varchar2
  ) IS
    file  utl_file.file_type;
  begin
    file := utl_file.fopen(directory, reportName, 'A');

    -- close the html tags etc 
    utl_file.put_line(file, '</body>');
    utl_file.put_line(file, '</html>');

    utl_file.fclose(file);
  end PRINT_FOOTER;

  -----------------------------------------------------------------------------=
  -- PRINT_EVENT_SUMMARY
  --   Displays a summary of avg statistics for a path
  -----------------------------------------------------------------------------=
  procedure PRINT_EVENT_SUMMARY(
    directory         in varchar2,
    reportName        in varchar2,
    comp_stat_table   in varchar2 default 'STREAMS$_ADVISOR_COMP_STAT',
    path_id           in number   default null,  -- show all stream paths
    bgn_run_id        in number   default -1,    -- show the last 10 runs
    end_run_id        in number   default -10
  )
  IS
  type cur_type is ref cursor;
  event_cur cur_type;
  event_stmt varchar2(4000);
  curr_path_id number;
  path_id_stmt varchar2(4000);
  path_id_cur cur_type;
  event_val number;
  event_name varchar2(500);
  comp_cur cur_type;
  comp_stmt varchar2(4000);
  comp_type varchar2(100);
  comp_acronym varchar2(100);
  comp_stype varchar2(100);
  comp_position number;
  stat STAT_PAIR;
  file  utl_file.file_type;
  indexName varchar2(200);
  begin

    if lower(comp_stat_table) = 'streams$_pa_show_comp_stat' then
      indexName := 'comp_stat_pkey';
    else
      indexName := lower(comp_stat_table) || '_pk';
    end if;

    file := utl_file.fopen(directory, reportName, 'A');

    utl_file.put_line(file,'<h2><a name="eventsummary">PATH LEVEL EVENT SUMMARY'
                          || '</a></h2>');
    utl_file.put_line(file, '<h5>(Click on Component acronym to view' 
                            || ' statistics for that component)</h5>');
    utl_file.put_line(file, '<table border=2>');
    utl_file.put_line(file, '<tr>');
    utl_file.put_line(file, '<th>Path Id</th>');
    utl_file.put_line(file, '<th>Component</th>'); 
    utl_file.put_line(file, '<th>Topevent 1</th>');
    utl_file.put_line(file, '<th>% Topevent 1</th>');
    utl_file.put_line(file, '<th>Topevent 2</th>');
    utl_file.put_line(file, '<th>% Topevent 2</th>');
    utl_file.put_line(file, '<th>Topevent 3</th>');
    utl_file.put_line(file, '<th>% Topevent 3</th>');
    utl_file.put_line(file, '<th>% Bottleneck</th>');
    utl_file.put_line(file, '</tr>');
    

    --if path_id is null extract for all paths
    path_id_stmt := 'select distinct path_id from ' || comp_stat_table ||' ';
    if path_id is not null then
      path_id_stmt := path_id_stmt || ' where path_id = ' || path_id;
    end if;
    path_id_stmt := path_id_stmt || '  order by path_id';

  
    begin 
      open path_id_cur for path_id_stmt;
      loop 
        fetch path_id_cur into curr_path_id;
        exit when path_id_cur%notfound;

        comp_stmt := 'select distinct  component_type, sub_component_type ' ||
                     ', position from ' || comp_stat_table ||
                     ' where path_id =' || curr_path_id ||
                     ' order by position';
            

         -- get all the components out there
         open comp_cur for comp_stmt;
         loop
           fetch comp_cur into comp_type, comp_stype, comp_position;
           exit when comp_cur%notfound;

           comp_acronym := get_acronym(comp_type,comp_stype);

           -- we are only interested in subcomponents
           continue when comp_acronym = 'CAPTURE' or comp_acronym = 'APPLY';

           event_stmt := 'select statistic_name, avg(statistic_value)' || 
                         ' from ' ||
                          comp_stat_table || 
                         ' where path_id = ' || curr_path_id ||
                         ' and  statistic_alias = ''S7'' ' ||
                         ' and advisor_run_id <= ' || end_run_id ||
                         ' and advisor_run_id >= ' || bgn_run_id ||
                         ' and component_type = ''' || comp_type || '''' ||
                             case when comp_acronym = 'PS' 
                                  or comp_acronym ='PR'
                                  or comp_stype is null
                             then ' and sub_component_type is null'
                             else 
                            ' and sub_component_type = ''' || comp_stype || ''''
                             end ||
                         ' and session_id is null  ' || 
                         ' and session_serial# is null ' ||
                         ' group by statistic_name ' ||
                         'order by avg(statistic_value) desc';

           open event_cur for event_stmt;

           utl_file.put_line(file, '<tr>');
  
           -- add the path id
           utl_file.put_line(file, '<td>' || curr_path_id || '</td>');

           -- add component name
           if comp_acronym != 'APS' and comp_acronym != 'LMP' then
             if comp_acronym = 'Q' or comp_acronym = 'PS' or
                comp_acronym = 'PR' then
               utl_file.put_line(file, '<td><a href="'|| reportName || '_'
                               || comp_acronym ||'at'|| comp_position||'.html">'
                               || comp_acronym ||'</a>' || '</td>');
             else
               utl_file.put_line(file, '<td><a href="'|| reportName || '_' 
                              || comp_acronym ||'.html">' ||
                              comp_acronym ||'</a>' || '</td>');
             end if;
           else
             stat := get_statistic_by_position(
                          comp_stat_table,'S4', curr_path_id, bgn_run_id,
                          comp_position, indexName);
              utl_file.put_line(file, '<td><a href="'|| reportName || '_' 
                            || comp_acronym || '.html">' ||
                            comp_acronym || '(' || stat.stat_value || ')</a>' 
                            || '</td>');
           end if;

           -- topevent 1
           fetch event_cur into event_name, event_val;
           if event_cur%notfound or event_val <= 0.0 then
             utl_file.put_line(file, '<td>&nbsp' || '</td>');
             utl_file.put_line(file, '<td>&nbsp' || '</td>');
           else
             
             utl_file.put_line(file, '<td>' || event_name || '</td>');
             utl_file.put_line(file, '<td>' || to_char(event_val, 
                                     'FM99999999.99') || '</td>');
           end if;
        
           -- topevent 2
           fetch event_cur into event_name, event_val;
           if event_cur%notfound or event_val <= 0.0 then
             utl_file.put_line(file, '<td>&nbsp' || '</td>');
             utl_file.put_line(file, '<td>&nbsp' || '</td>');
           else
             utl_file.put_line(file, '<td>' || event_name || '</td>');
             utl_file.put_line(file, '<td>' || to_char(event_val, 
                                     'FM99999999.99') 
                                     || '</td>');
           end if;

           -- topevent 3
           fetch event_cur into event_name, event_val;
           if event_cur%notfound or event_val <= 0.0 then
             utl_file.put_line(file, '<td>&nbsp' || '</td>');
             utl_file.put_line(file, '<td>&nbsp' || '</td>');
           else
             utl_file.put_line(file, '<td>' || event_name || '</td>');
             utl_file.put_line(file, '<td>' || 
                                     to_char(event_val, 'FM99999999.99') 
                                     || '</td>');
           end if;

           -- % of time this event was the bottleneck
           utl_file.put_line(file, '<td>' 
                        || to_char(get_bottleneck_percent(comp_acronym, 
                        curr_path_id, bgn_run_id, end_run_id),'FM99999999.99') 
                        || '</td>');

           -- close the row
           utl_file.put_line(file, '</tr>');
           

           close event_cur;        

         end loop;
         close comp_cur;  
       end loop;
       close path_id_cur;
    end;
    -- close the table
    utl_file.put_line(file, '</table>');
    utl_file.put_line(file, '<br>');
    utl_file.fclose(file);
  end PRINT_EVENT_SUMMARY;
 
  -----------------------------------------------------------------------------=
  -- PRINT_PATH_LEVEL_SUMMARY
  --   Displays a summary of avg statistics for a path
  -----------------------------------------------------------------------------=
  procedure PRINT_PATH_LEVEL_SUMMARY( 
    directory         in varchar2,
    reportName        in varchar2,
    comp_stat_table   in varchar2 default 'STREAMS$_ADVISOR_COMP_STAT',
    path_id           in number   default null,  -- show all stream paths
    bgn_run_id        in number   default -1,    -- show the last 10 runs
    end_run_id        in number   default -10
  )
  IS
  type cur_type is ref cursor;
  comp_cur cur_type;
  comp_stmt varchar2(4000);
  comp_type varchar2(100);
  comp_stype varchar2(100);
  comp_position number;
  stat_cur cur_type;
  curr_path_id number;
  path_id_stmt varchar2(4000);
  path_id_cur cur_type;
  stat number;
  stat_alias varchar2(10);
  comp_acronym varchar2(20);
  file  utl_file.file_type;
  begin
    file := utl_file.fopen(directory, reportName, 'A');

    utl_file.put_line(file, '<h2><a name="pathsummary">PATH LEVEL SUMMARY'
                          || '</a></h2>');
 
    utl_file.put_line(file, '<table border=2>');
    utl_file.put_line(file, '<tr>');
    utl_file.put_line(file, '<th>Path Id</th>'); 
    utl_file.put_line(file, '<th>Avg CAPTURE msgs/sec</th>');
    utl_file.put_line(file, '<th>Avg CAPTURE latency</th>');
    utl_file.put_line(file, '<th>Avg APPLY txns/sec</th>');
    utl_file.put_line(file, '<th>Avg APPLY msgs/sec</th>');
    utl_file.put_line(file, '<th>Avg APPLY latency</th>');
    utl_file.put_line(file, '</tr>');
    

    --if path_id is null extract for all paths
    path_id_stmt := 'select distinct path_id from ' || comp_stat_table ||' ';
    if path_id is not null then
      path_id_stmt := path_id_stmt || ' where path_id = ' || path_id;
    end if;
    path_id_stmt := path_id_stmt || '  order by path_id';

    begin 
      open path_id_cur for path_id_stmt;
      loop 
        fetch path_id_cur into curr_path_id;
        exit when path_id_cur%notfound;

           utl_file.put_line(file, '<tr>');
           
           -- add the path id
           utl_file.put_line(file, '<td>' || curr_path_id || '</td>');

         
           -- CAP msgs/sec
           stat := get_avg_statistic_by_component(
                            comp_stat_table,'S2', curr_path_id, bgn_run_id,
                            end_run_id, 'CAPTURE', null);
           if stat >= 0  then
             utl_file.put_line(file, '<td>' || to_char(stat, 'FM99999999.99') 
                                   || '</td>');
           else
             utl_file.put_line(file, '<td>&nbsp' || '</td>');
           end if;

           -- CAP Latency
           stat := get_avg_statistic_by_component(
                            comp_stat_table,'S3', curr_path_id, bgn_run_id,
                            end_run_id, 'CAPTURE', null);
          
           if stat >= 0 then
             utl_file.put_line(file, '<td>' || to_char(stat, 'FM99999999.99') 
                                   || '</td>');
           else
             utl_file.put_line(file, '<td>&nbsp' || '</td>');
           end if;

           -- Apply txns/sec
           stat := get_avg_statistic_by_component(
                            comp_stat_table,'S2', curr_path_id, bgn_run_id,
                            end_run_id, 'APPLY', null);
          
           if stat >= 0 then
             utl_file.put_line(file, '<td>' || to_char(stat, 'FM99999999.99') 
                                  || '</td>');
           else
             utl_file.put_line(file, '<td>&nbsp' || '</td>');
           end if;

           -- Apply msgs/sec
           stat := get_avg_statistic_by_component(
                            comp_stat_table,'S1', curr_path_id, bgn_run_id,
                            end_run_id, 'APPLY', null);
          
           if stat >= 0 then
             utl_file.put_line(file, '<td>' || to_char(stat, 'FM99999999.99') 
                                   || '</td>');
           else
             utl_file.put_line(file, '<td>&nbsp' || '</td>');
           end if;

           -- Apply Latency
           stat := get_avg_statistic_by_component(
                            comp_stat_table,'S3', curr_path_id, bgn_run_id,
                            end_run_id, 'APPLY', null);
         
           if stat >= 0 then
             utl_file.put_line(file, '<td>' || to_char(stat, 'FM99999999.99') 
                                  || '</td>');
           else
             utl_file.put_line(file, '<td>&nbsp' || '</td>');
           end if;

           -- close the row
          utl_file.put_line(file, '</tr>');
          

      end loop;
      close path_id_cur;
    end;
    -- close the table
    utl_file.put_line(file, '</table>');
    utl_file.put_line(file, '<br>');
    utl_file.fclose(file);
  end PRINT_PATH_LEVEL_SUMMARY;

  -----------------------------------------------------------------------------=
  -- PRINT_RATE_LEVEL_STATS
  --   Displays a table of statistics at the rate level
  -----------------------------------------------------------------------------=
  procedure PRINT_RATE_LEVEL_STATS(
    directory         in varchar2,
    reportName        in varchar2,
    comp_stat_table   in varchar2 default 'STREAMS$_ADVISOR_COMP_STAT',
    path_id           in number   default null,  -- show all stream paths
    bgn_run_id        in number   default -1,    -- show the last 10 runs
    end_run_id        in number   default -10
  )
  IS
  type cur_type is ref cursor;
  comp_cur cur_type;
  comp_stmt varchar2(4000);
  comp_type varchar2(100);
  comp_stype varchar2(100);
  comp_position number;
  stat_cur cur_type;
  run_id number;
  run_time_cur cur_type;
  run_time varchar2(50);
  curr_path_id number;
  path_id_stmt varchar2(4000);
  path_id_cur cur_type;
  stat STAT_PAIR default null;
  stat_alias varchar2(10);
  comp_acronym varchar2(20);
  bottleneckInfo varchar2(4000);
  bottleneck_cur cur_type;
  file  utl_file.file_type;
  begin
    file := utl_file.fopen(directory, reportName, 'A');
    bottleneckInfo := null;

    utl_file.put_line(file, '<h2><a name="ratesummary">RATE LEVEL STATS' 
                             || '</a></h2>');
    utl_file.put_line(file, '<h5>(Click on each row to view stats for the' 
                            || ' specific run)</h5>');
    utl_file.put_line(file, '<table border=2>');
    utl_file.put_line(file, '<tr>');
    utl_file.put_line(file, '<th>Path Id</th>'); 
    utl_file.put_line(file, '<th>Run Id</th>');
    utl_file.put_line(file, '<th>Run time</th>');
    utl_file.put_line(file, '<th>CAP msgs/sec</th>');
    utl_file.put_line(file, '<th>CAP latency</th>');
    utl_file.put_line(file, '<th>APPLY txns/sec</th>');
    utl_file.put_line(file, '<th>APPLY msgs/sec</th>');
    utl_file.put_line(file, '<th>APPLY latency</th>');
    utl_file.put_line(file, '<th>Bottleneck</th>');
    utl_file.put_line(file, '</tr>');
    

    --if path_id is null extract for all paths
    path_id_stmt := 'select distinct path_id from ' || comp_stat_table ||' ';
    if path_id is not null then
      path_id_stmt := path_id_stmt || ' where path_id = ' || path_id;
    end if;
    path_id_stmt := path_id_stmt || '  order by path_id';

    begin 
      open path_id_cur for path_id_stmt;
      loop 
        fetch path_id_cur into curr_path_id;
        exit when path_id_cur%notfound;
        -- for every run
        for run_id in bgn_run_id..end_run_id loop

           utl_file.put_line(file, '<tr onclick="DoNav(''' || reportName 
                     || '_' || curr_path_id || '_' || run_id 
                     ||'.html'');" onmouseout="ChangeColor(this,false);"' ||
                    ' onmouseover="ChangeColor(this,true);">');
           
           -- add the path id
           utl_file.put_line(file, '<td>' || curr_path_id || '</td>');

           -- add the run id
           utl_file.put_line(file, '<td>' || run_id || '</td>');

           -- add the run time
           open run_time_cur for 'select distinct ' || 
                    'to_char( advisor_run_time,''DD-MON-YYYY HH24:MI:SS'') ' ||
                    ' from ' || comp_stat_table || 
                    ' where advisor_run_id = ' || run_id ||
                    ' and path_id = '||curr_path_id;
           fetch run_time_cur into run_time;
           close run_time_cur;
           utl_file.put_line(file, '<td>' || run_time || '</td>');

           -- CAP msgs/sec
           stat := get_statistic_by_component(
                            comp_stat_table,'S2', curr_path_id, run_id,
                            'CAPTURE', null);
           if stat.stat_name is not null then
             utl_file.put_line(file, '<td>' || 
                         to_char(stat.stat_value, 'FM99999999') || '</td>');
           else
             utl_file.put_line(file, '<td>&nbsp' || '</td>');
           end if;

           -- CAP Latency
           stat := get_statistic_by_component(
                            comp_stat_table,'S3', curr_path_id, run_id,
                            'CAPTURE', null);
           if stat.stat_name is not null then
             utl_file.put_line(file, '<td>' || 
                         to_char(stat.stat_value, 'FM99999999') || '</td>');
           else
             utl_file.put_line(file, '<td>&nbsp' || '</td>');
           end if;

           -- Apply txns/sec
           stat := get_statistic_by_component(
                            comp_stat_table,'S2', curr_path_id, run_id,
                            'APPLY', null);
           if stat.stat_name is not null then
             utl_file.put_line(file, '<td>' || 
                         to_char(stat.stat_value, 'FM99999999') || '</td>');
           else
             utl_file.put_line(file, '<td>&nbsp' || '</td>');
           end if;

           -- Apply msgs/sec
           stat := get_statistic_by_component(
                            comp_stat_table,'S1', curr_path_id, run_id,
                            'APPLY', null);
           if stat.stat_name is not null then
             utl_file.put_line(file, '<td>' || 
                         to_char(stat.stat_value, 'FM99999999') || '</td>');
           else
             utl_file.put_line(file, '<td>&nbsp' || '</td>');
           end if;

           -- Apply Latency
           stat := get_statistic_by_component(
                            comp_stat_table,'S3', curr_path_id, run_id,
                            'APPLY', null);
           if stat.stat_name is not null then
             utl_file.put_line(file, '<td>' || 
                         to_char(stat.stat_value, 'FM99999999') || '</td>');
           else
             utl_file.put_line(file, '<td>&nbsp' || '</td>');
           end if;

           -- Bottleneck
           utl_file.put(file, '<td>');
           open bottleneck_cur for 'select B.spare3 ' || 
                                   ' from streams$_pa_path_bottleneck B ' ||
                                   ' where B.advisor_run_id = ' || run_id || 
                                   ' and B.path_id = ' || curr_path_id;
           loop
             fetch bottleneck_cur into bottleneckInfo;
             exit when bottleneck_cur%notfound;
             utl_file.put(file, bottleneckInfo || ',');
           end loop;
           
           if bottleneckInfo is null then
             utl_file.put(file, '&nbsp');
           end if;

           close bottleneck_cur;

           utl_file.put_line(file, '</td>');
           -- close the row
           utl_file.put_line(file, '</tr>');
           
         
        end loop;
      end loop;
      close path_id_cur;
    end;
    -- close the table
    utl_file.put_line(file, '</table>');
    utl_file.put_line(file, '<br>');
    utl_file.fclose(file);
  end PRINT_RATE_LEVEL_STATS;

  -----------------------------------------------------------------------------=
  -- PRINT_COMPONENT_LEVEL_STATS
  --   Displays a table of statistics at the component level
  -----------------------------------------------------------------------------=
  procedure PRINT_COMPONENT_LEVEL_STATS(
    directory         in varchar2,
    reportName        in varchar2,
    comp_stat_table   in varchar2 default 'STREAMS$_ADVISOR_COMP_STAT',
    path_id           in number   default null,  -- show all stream paths
    bgn_run_id        in number   default -1,    -- show the last 10 runs
    end_run_id        in number   default -10
  )
  IS
  type cur_type is ref cursor;
  comp_cur cur_type;
  comp_stmt varchar2(4000);
  comp_type varchar2(100);
  comp_stype varchar2(100);
  comp_position number;
  stat_cur cur_type;
  run_time_cur cur_type;
  run_id number;
  run_time varchar2(50);
  curr_path_id number;
  path_id_stmt varchar2(4000);
  path_id_cur cur_type;
  stat STAT_PAIR default null;
  stat_alias varchar2(10);
  comp_acronym varchar2(20);
  event_stmt varchar2(4000);
  event_cur  cur_type;
  event_name varchar2(200);
  event_val number;
  file  utl_file.file_type; 
  fileName varchar2(200);
  indexName varchar2(100);
  begin

    if lower(comp_stat_table) = 'streams$_pa_show_comp_stat' then
      indexName := 'comp_stat_pkey';
    else
      indexName := lower(comp_stat_table) || '_pk';
    end if;

    --if path_id is null extract for all paths
    path_id_stmt := 'select /* INDEX(' || comp_stat_table || ' ' ||
                    indexName || ') */ ' || 
                   ' distinct path_id from ' || comp_stat_table || ' ' ;
    if path_id is not null then
      path_id_stmt := path_id_stmt || ' where path_id = ' || path_id;
    end if;
    path_id_stmt := path_id_stmt || '  order by path_id';

    comp_stmt := 'select /* INDEX(' || comp_stat_table || ' ' ||
                  indexName || ') */ ' ||
                  ' distinct  component_type, sub_component_type ' ||
                  ', position from ' || comp_stat_table ||
                  ' order by position';

    -- get all the components out there
    open comp_cur for comp_stmt;
      loop
        fetch comp_cur into comp_type, comp_stype, comp_position;
        exit when comp_cur%notfound;
        
        -- open file for component
        comp_acronym := get_acronym(comp_type,comp_stype);

        if comp_acronym = 'Q' or comp_acronym = 'PS' 
           or comp_acronym = 'PR' then
          fileName :=  comp_acronym ||'at' || comp_position;
        else
          fileName := comp_acronym;
        end if;

        fileName := reportName || '_' || fileName || '.html';
        print_header(directory, fileName);
        file := utl_file.fopen(directory, fileName, 'A');

        utl_file.put_line(file,'<h2><a name="complevelstats">' 
                               || 'COMPONENT LEVEL STATS' 
                               || '</a></h2>');
        utl_file.put_line(file, '<b><a name = "'||  fileName || '">'
                              ||comp_acronym ||'</a></b>');

         -- create a table
        utl_file.put_line(file, '<table border=2>');
        utl_file.put_line(file, '<tr>');
        utl_file.put_line(file, '<th>Path Id</th>');
        utl_file.put_line(file, '<th>Run Id</th>'); 
        utl_file.put_line(file, '<th>Run time</th>');
        utl_file.put_line(file, '<th>Throughput</th>');

        -- display 'rate 2' as actual statistic
        if comp_acronym = 'CAPTURE' then
          utl_file.put_line(file, '<th>Captured/sec</th>');
        elsif comp_acronym = 'Q' then
          utl_file.put_line(file, '<th>No of msgs</th>');
        elsif comp_acronym = 'PS' then
          utl_file.put_line(file, '<th>bytes/sec</th>');
        elsif comp_acronym = 'APPLY' then
          utl_file.put_line(file, '<th>txns applied/sec</th>');
        else
          utl_file.put_line(file, '<th>Rate 2</th>');
        end if;

        utl_file.put_line(file, '<th>Latency</th>');
        utl_file.put_line(file, '<th>% Idle</th>');
        utl_file.put_line(file, '<th>% Flwctrl</th>');
        utl_file.put_line(file, '<th>% Topevent 1</th>');
        utl_file.put_line(file, '<th>Topevent 1</th>');
        utl_file.put_line(file, '<th>% Topevent 2</th>');
        utl_file.put_line(file, '<th>Topevent 2</th>');
        utl_file.put_line(file, '<th>% Topevent 3</th>');
        utl_file.put_line(file, '<th>Topevent 3</th>');
        utl_file.put_line(file, '</tr>');
    
        begin 
          open path_id_cur for path_id_stmt;
          loop 
            -- for every path
            fetch path_id_cur into curr_path_id;
            exit when path_id_cur%notfound;
            
            -- for every run
            for run_id in bgn_run_id..end_run_id loop

              utl_file.put_line(file, '<tr onclick="DoNav(''' || reportName 
                          || '_' || curr_path_id || '_' || run_id 
                          ||'.html'');" onmouseout="ChangeColor(this,false);"' 
                          || ' onmouseover="ChangeColor(this,true);">');
           
              -- add the path id
              utl_file.put_line(file, '<td>' || curr_path_id || '</td>');

              -- add the run id
              utl_file.put_line(file, '<td>' || run_id || '</td>');

              -- add the run time 
              open run_time_cur for 'select /* INDEX(' || comp_stat_table || 
                    ' ' || indexName || ') */ ' ||' distinct ' || 
                    'to_char( advisor_run_time,''DD-MON-YYYY HH24:MI:SS'') ' ||
                    ' from ' || comp_stat_table || 
                    ' where advisor_run_id = ' || run_id ||
                    ' and path_id = '||curr_path_id;
              fetch run_time_cur into run_time;
              close run_time_cur;
              utl_file.put_line(file, '<td>' || run_time || '</td>');

              -- populate throughput
              if comp_acronym = 'CAPTURE' then          
              -- for capture display enqueued/sec
                stat_alias := 'S2';
              else
                stat_alias := 'S1';
              end if;
          
              stat := get_statistic_by_position(
                            comp_stat_table,stat_alias, curr_path_id, run_id,
                            comp_position, indexName);
         
              if stat.stat_name is not null then
                utl_file.put_line(file, '<td>' || 
                            to_char(stat.stat_value, 'FM99999999') || '</td>');
              else
                utl_file.put_line(file, '<td>&nbsp' || '</td>');
              end if;

              -- populate rate2 
              if comp_acronym = 'CAPTURE' then
                -- for capture display captured/sec
                stat_alias := 'S1';
              elsif comp_acronym = 'Q' then
                stat_alias := 'S3';
              else
                stat_alias := 'S2';
              end if;
          
              stat := get_statistic_by_position(
                            comp_stat_table,stat_alias, curr_path_id, run_id,
                            comp_position, indexName);
         
              if stat.stat_name is not null then
                utl_file.put_line(file, '<td>' || 
                            to_char(stat.stat_value, 'FM99999999') || '</td>');
              else
                utl_file.put_line(file, '<td>&nbsp' || '</td>');
              end if;



              -- populate latency
              if comp_acronym != 'Q' then 
                -- for Queues, S3 mean something else
                stat := get_statistic_by_position(
                               comp_stat_table,'S3', curr_path_id, run_id,
                               comp_position, indexName);
         
                 if stat.stat_name is not null then
                   utl_file.put_line(file, '<td>' || 
                               to_char(stat.stat_value, 'FM99999999') || 
                               '</td>');
                 else
                   utl_file.put_line(file, '<td>&nbsp' || '</td>');
                 end if;
              else
                 utl_file.put_line(file, '<td>&nbsp' || '</td>');
              end if;


              -- populate idle %
              stat := get_statistic_by_position(
                             comp_stat_table,'S5', curr_path_id, run_id,
                             comp_position, indexName);
         
              if stat.stat_name is not null then
                utl_file.put_line(file, '<td>' || 
                            to_char(stat.stat_value, 'FM99999999D9') || 
                            '</td>');
              else
                utl_file.put_line(file, '<td>&nbsp' || '</td>');
              end if;

              -- populate flwctrl%
              stat := get_statistic_by_position(
                            comp_stat_table,'S6', curr_path_id, run_id,
                            comp_position, indexName);
              if stat.stat_name is not null then
                utl_file.put_line(file, '<td>' || 
                            to_char(stat.stat_value, 'FM99999999D9') || 
                            '</td>');
              else
                utl_file.put_line(file, '<td>&nbsp' || '</td>');
              end if;
          
             
              -- populate topevents%,topevents

              event_stmt := 'select /* INDEX(' || comp_stat_table || ' ' ||
                            indexName || ') */ ' || 
                            ' statistic_name, statistic_value' || 
                            ' from ' ||
                             comp_stat_table || 
                            ' where path_id = ' || curr_path_id ||
                            ' and  statistic_alias = ''S7'' ' ||
                            ' and position = '|| comp_position ||
                            ' and advisor_run_id = ' || run_id ||
                            ' and component_type = ''' || comp_type || '''' ||
                             case when comp_acronym = 'PS' 
                                   or comp_acronym ='PR'
                                   or comp_stype is null
                               then ' and sub_component_type is null'
                               else ' and sub_component_type = ''' || 
                                    comp_stype || ''''
                             end ||
                            ' and session_id is null  ' || 
                            ' and session_serial# is null ' ||
                            ' order by statistic_value desc';

              open event_cur for event_stmt;

           
              -- topevent 1
              fetch event_cur into event_name, event_val;
              if event_cur%notfound or event_val <= 0.0 then
                utl_file.put_line(file, '<td>&nbsp' || '</td>');
                utl_file.put_line(file, '<td>&nbsp' || '</td>');
              else
                utl_file.put_line(file, '<td>' || 
                            to_char(event_val, 'FM99999999.99') || '</td>');
                utl_file.put_line(file, '<td>' || event_name || '</td>');
              end if;
        
              -- topevent 2
              fetch event_cur into event_name, event_val;
              if event_cur%notfound or event_val <= 0.0 then
                utl_file.put_line(file, '<td>&nbsp' || '</td>');
                utl_file.put_line(file, '<td>&nbsp' || '</td>');
              else
                utl_file.put_line(file, '<td>' || 
                            to_char(event_val, 'FM99999999.99') || '</td>');
                utl_file.put_line(file, '<td>' || event_name || '</td>');
              end if;

              -- topevent 3
              fetch event_cur into event_name, event_val;
              if event_cur%notfound or event_val <= 0.0 then
                utl_file.put_line(file, '<td>&nbsp' || '</td>');
                utl_file.put_line(file, '<td>&nbsp' || '</td>');
              else
                utl_file.put_line(file, '<td>' || 
                            to_char(event_val, 'FM99999999.99') || '</td>');
                utl_file.put_line(file, '<td>' || event_name || '</td>');
              end if;
 
              close event_cur;

              utl_file.put_line(file, '</tr>');
              

            end loop; -- ends for 
          end loop;
          close path_id_cur;
        end;-- table ends when stats are printed for all pathids and runids
        utl_file.put_line(file, '</table>');
        utl_file.put_line(file, '<br>');
        utl_file.put_line(file,'<h3><a href="'|| reportName ||'">' 
                               || 'Back to Report Home' 
                               || '</a></h3>');
        utl_file.fclose(file);
        print_footer(directory, fileName);
      end loop;
    close comp_cur;

  end PRINT_COMPONENT_LEVEL_STATS;
 

  -----------------------------------------------------------------------------=
  -- PRINT_RUN_LEVEL_STATS
  --   Displays a table of statistics at the run level
  -----------------------------------------------------------------------------=
  procedure PRINT_RUN_LEVEL_STATS(
    directory         in varchar2,
    reportName        in varchar2,
    comp_stat_table   in varchar2 default 'STREAMS$_ADVISOR_COMP_STAT',
    path_id           in number   default null,  -- show all stream paths
    bgn_run_id        in number   default -1,    -- show the last 10 runs
    end_run_id        in number   default -10
  )
  IS
  type cur_type is ref cursor;
  comp_cur cur_type;
  comp_stmt varchar2(4000);
  comp_type varchar2(100);
  comp_stype varchar2(100);
  comp_position number;
  stat_cur cur_type;
  run_id number;
  run_time varchar2(50);
  curr_path_id number;
  path_id_stmt varchar2(4000);
  path_id_cur cur_type;
  stat STAT_PAIR default null;
  stat_alias varchar2(10);
  comp_acronym varchar2(20);
  event_stmt varchar2(4000);
  event_cur  cur_type;
  event_name varchar2(200);
  event_val number;
  file  utl_file.file_type;
  fileName varchar2(200);
  indexName varchar2(200);
  --NOTE:  Assumes there are no more than 30 components in a run
  type compTypeArr is varray(30) of varchar2(100);
  type compStypeArr is varray(30) of varchar2(100);
  type compPositionArr is varray(30) of number;

  comp_types compTypeArr;
  comp_stypes compStypeArr;
  comp_positions compPositionArr;
  i number;
  begin

     --if path_id is null extract for all paths
    path_id_stmt := 'select /* INDEX(' || comp_stat_table || ' ' ||
                    indexName || ') */ ' ||
                    ' distinct path_id from ' ||comp_stat_table || ' ';
    if path_id is not null then
      path_id_stmt := path_id_stmt || ' where path_id = ' || path_id;
    end if;
    path_id_stmt := path_id_stmt || '  order by path_id';

    if lower(comp_stat_table) = 'streams$_pa_show_comp_stat' then
      indexName := 'comp_stat_pkey';
    else
      indexName := lower(comp_stat_table) || '_pk';
    end if;


    begin 
      open path_id_cur for path_id_stmt;
      loop 
        fetch path_id_cur into curr_path_id;
        exit when path_id_cur%notfound;

        -- store all the component information for the path
        comp_types := compTypeArr();
        comp_stypes := compStypeArr();
        comp_positions := compPositionArr();

        comp_stmt := 'select /* INDEX(' || comp_stat_table || ' ' ||
                    indexName || ') */ ' ||
                     'distinct  component_type, sub_component_type ' ||
                     ', position from ' || comp_stat_table ||
                     ' where path_id =' || curr_path_id ||
                     ' order by position';
        open comp_cur for comp_stmt;
        loop
          fetch comp_cur into comp_type, comp_stype, comp_position;
          exit when comp_cur%notfound;
          comp_types.extend(1);
          comp_stypes.extend(1);
          comp_positions.extend(1);
          comp_types(comp_types.last) := comp_type;
          comp_stypes(comp_stypes.last) := comp_stype;
          comp_positions(comp_positions.last) := comp_position;
        end loop;
        close comp_cur;
               

        -- for every run
        for run_id in bgn_run_id..end_run_id loop

          fileName := reportName ||'_' || curr_path_id || '_'
                      || run_id || '.html';
          print_header(directory, fileName);
          file := utl_file.fopen(directory, fileName, 'A');

          utl_file.put_line(file, '<h2><a name = "runlevelstats">' 
                                  || 'RUN LEVEL STATS' ||'</a></h2>');

          comp_stmt := 'select /* INDEX(' || comp_stat_table || ' ' ||
                       indexName || ') */ ' ||
                       'distinct  component_type, sub_component_type ' ||
                       ', position from ' || comp_stat_table ||
                       ' where path_id =' || curr_path_id ||
                       ' and advisor_run_id =' || run_id ||
                       ' order by position';
      
          open comp_cur for 'select /* INDEX(' || comp_stat_table || ' ' ||
                    indexName || ') */ ' ||
                    'distinct ' || 
                    'to_char(advisor_run_time,''DD-MON-YYYY HH24:MI:SS'') ' ||
                    ' from ' || comp_stat_table || 
                    ' where advisor_run_id = ' || run_id ||
                    ' and path_id = '||curr_path_id;
          fetch comp_cur into run_time;
          close comp_cur;

          utl_file.put_line(file, '<b><a name="'|| curr_path_id || '-' 
                               || run_id ||'">'|| 'Path: ' || curr_path_id 
                               || ' Run id: ' || run_id  ||
                               ' Run time: ' || run_time || '</a></b>');

          utl_file.put_line(file, '<table border=2>');
          utl_file.put_line(file, '<tr>');
          utl_file.put_line(file, '<th>Component</th>'); 
          utl_file.put_line(file, '<th>Throughput</th>');
          utl_file.put_line(file, '<th>Rate 2</th>');
          utl_file.put_line(file, '<th>Latency</th>');
          utl_file.put_line(file, '<th>% Idle </th>');
          utl_file.put_line(file, '<th>% Flwctrl </th>');
          utl_file.put_line(file, '<th>% Topevent 1</th>');
          utl_file.put_line(file, '<th>Topevent 1</th>');
          utl_file.put_line(file, '<th>% Topevent 2</th>');
          utl_file.put_line(file, '<th>Topevent 2</th>');
          utl_file.put_line(file, '<th>% Topevent 3</th>');
          utl_file.put_line(file, '<th>Topevent 3</th>');
          utl_file.put_line(file, '</tr>');


         -- get all the components out there
         for i in comp_types.first .. comp_types.last 
         loop
           comp_type := comp_types(i);
           comp_stype :=  comp_stypes(i);
           comp_position := comp_positions(i);
       
           comp_acronym := get_acronym(comp_type,comp_stype);

           -- for every such component extract the data
           utl_file.put_line(file, '<tr>');

           -- populate component name
           if comp_acronym != 'APS' and comp_acronym != 'LMP' then
             if comp_acronym = 'Q' or comp_acronym = 'PS' or
                comp_acronym = 'PR' then
                 utl_file.put_line(file, '<td><a href="' || reportName || '_'
                               || comp_acronym ||'at'|| 
                               comp_position||'.html">' ||
                               comp_acronym ||'</a>' || '</td>');
             else
               utl_file.put_line(file, '<td><a href="' || reportName || '_' 
                               || comp_acronym ||'.html">' ||
                              comp_acronym ||'</a>' || '</td>');
             end if;
           else
             stat := get_statistic_by_position(
                          comp_stat_table,'S4', curr_path_id, run_id,
                          comp_position, indexName);
              utl_file.put_line(file, '<td><a href="'|| reportName || '_' 
                            || comp_acronym || '.html">' ||
                            comp_acronym || '(' || stat.stat_value || ')</a>' 
                            || '</td>');
           end if;

           -- populate throughput
           if comp_acronym = 'CAPTURE' then
             -- for capture display enqueued/sec
             stat_alias := 'S2';
           else
             stat_alias := 'S1';
           end if;
          
          stat := get_statistic_by_position(
                          comp_stat_table,stat_alias, curr_path_id, run_id,
                          comp_position, indexName);
         
           if stat.stat_name is not null then
             utl_file.put_line(file, '<td>' || 
                         to_char(stat.stat_value, 'FM99999999') || '</td>');
           else
             utl_file.put_line(file, '<td>&nbsp' || '</td>');
           end if;

           -- populate rate2 
          if comp_acronym = 'CAPTURE' then
             -- for capture display captured/sec
             stat_alias := 'S1';
           elsif comp_acronym = 'Q' then
             stat_alias := 'S3';
           else
             stat_alias := 'S2';
           end if;
          
           stat := get_statistic_by_position(
                          comp_stat_table,stat_alias, curr_path_id, run_id,
                          comp_position, indexName);
         
           if stat.stat_name is not null then
             utl_file.put_line(file, '<td>' || 
                         to_char(stat.stat_value, 'FM99999999') || '</td>');
           else
             utl_file.put_line(file, '<td>&nbsp' || '</td>');
           end if;



           -- populate latency
           if comp_acronym != 'Q' then 
             -- for Queues, S3 mean something else
             stat := get_statistic_by_position(
                            comp_stat_table,'S3', curr_path_id, run_id,
                            comp_position, indexName);
         
             if stat.stat_name is not null then
               utl_file.put_line(file, '<td>' || 
                           to_char(stat.stat_value, 'FM99999999') || '</td>');
             else
               utl_file.put_line(file, '<td>&nbsp' || '</td>');
             end if;
           else
              utl_file.put_line(file, '<td>&nbsp' || '</td>');
           end if;


           -- populate idle %
           stat := get_statistic_by_position(
                          comp_stat_table,'S5', curr_path_id, run_id,
                          comp_position, indexName);
         
           if stat.stat_name is not null then
             utl_file.put_line(file, '<td>' || 
                         to_char(stat.stat_value, 'FM99999999D9') || '</td>');
           else
             utl_file.put_line(file, '<td>&nbsp' || '</td>');
           end if;
           -- populate flwctrl%
           stat := get_statistic_by_position(
                          comp_stat_table,'S6', curr_path_id, run_id,
                          comp_position, indexName);
           if stat.stat_name is not null then
             utl_file.put_line(file, '<td>' || 
                         to_char(stat.stat_value, 'FM99999999D9') || '</td>');
           else
             utl_file.put_line(file, '<td>&nbsp' || '</td>');
           end if;
          
           -- populate topevents%,topevents

           event_stmt := 'select /* INDEX(' || comp_stat_table || ' ' ||
                         indexName || ') */ ' || 
                         ' statistic_name, statistic_value' || 
                         ' from ' ||
                          comp_stat_table || 
                         ' where path_id = ' || curr_path_id ||
                         ' and  statistic_alias = ''S7'' ' ||
                         ' and position = '|| comp_position ||
                         ' and advisor_run_id = ' || run_id ||
                         ' and component_type = ''' || comp_type || '''' ||
                         case when comp_acronym = 'PS' or comp_acronym ='PR'
                                    or comp_stype is null
                               then ' and sub_component_type is null'
                               else ' and sub_component_type = ''' || 
                                    comp_stype || ''''
                         end ||
                         ' and session_id is null  ' || 
                         ' and session_serial# is null ' ||
                         ' order by statistic_value desc';

           open event_cur for event_stmt;

          
           -- topevent 1
           fetch event_cur into event_name, event_val;
           if event_cur%notfound or event_val <= 0.0 then
             utl_file.put_line(file, '<td>&nbsp' || '</td>');
             utl_file.put_line(file, '<td>&nbsp' || '</td>');
           else
             utl_file.put_line(file, '<td>' || to_char(event_val, 
                                     'FM99999999.99') || '</td>');
             utl_file.put_line(file, '<td>' || event_name || '</td>');
           end if;
        
           -- topevent 2
           fetch event_cur into event_name, event_val;
           if event_cur%notfound or event_val <= 0.0 then
             utl_file.put_line(file, '<td>&nbsp' || '</td>');
             utl_file.put_line(file, '<td>&nbsp' || '</td>');
           else
             utl_file.put_line(file, '<td>' || to_char(event_val, 
                                     'FM99999999.99') || '</td>');
             utl_file.put_line(file, '<td>' || event_name || '</td>');
           end if;

           -- topevent 3
           fetch event_cur into event_name, event_val;
           if event_cur%notfound or event_val <= 0.0 then
             utl_file.put_line(file, '<td>&nbsp' || '</td>');
             utl_file.put_line(file, '<td>&nbsp' || '</td>');
           else
             utl_file.put_line(file, '<td>' || to_char(event_val, 
                                    'FM99999999.99') || '</td>');
             utl_file.put_line(file, '<td>' || event_name || '</td>');
           end if;
 
           close event_cur;

           utl_file.put_line(file, '</tr>');
           
         end loop;
         utl_file.put_line(file, '</table>');
         utl_file.put_line(file, '<br>');
         utl_file.put_line(file,'<h3><a href="'|| reportName ||'">' 
                               || 'Back to Report Home' 
                               || '</a></h3>');
         utl_file.fclose(file);
         print_footer(directory, fileName);
       end loop;

       -- clear all the component info for the path
       comp_types.delete();
       comp_stypes.delete();
       comp_positions.delete();
     end loop;
     close path_id_cur;
    end;
  end PRINT_RUN_LEVEL_STATS;


  -----------------------------------------------------------------------------=
  -- PRINT_RUN_AND_COMP_LEVEL_STATS
  --   Displays a table of statistics at the run level and component level
  --   clubbed into one to improve performance
  -----------------------------------------------------------------------------=
  procedure PRINT_RUN_AND_COMP_LEVEL_STATS(
    directory         in varchar2,
    reportName        in varchar2,
    comp_stat_table   in varchar2 default 'STREAMS$_ADVISOR_COMP_STAT',
    path_id           in number   default null,  -- show all stream paths
    bgn_run_id        in number   default -1,    -- show the last 10 runs
    end_run_id        in number   default -10
  )
  IS
  type cur_type is ref cursor;
  comp_cur cur_type;
  comp_stmt varchar2(4000);
  comp_type varchar2(100);
  comp_stype varchar2(100);
  comp_position number;
  stat_cur cur_type;
  run_id number;
  run_time varchar2(50);
  curr_path_id number;
  path_id_stmt varchar2(4000);
  path_id_cur cur_type;
  stat STAT_PAIR default null;
  stat_alias varchar2(10);
  comp_acronym varchar2(20);
  event_stmt varchar2(4000);
  event_cur  cur_type;
  event_name varchar2(200);
  event_val number;
  file  utl_file.file_type;
  fileName varchar2(200);
  indexName varchar2(200);
  --NOTE:  Assumes there are no more than 30 components in a run
  type compTypeArr is varray(30) of varchar2(100);
  type compStypeArr is varray(30) of varchar2(100);
  type compPositionArr is varray(30) of number;
  type fileArr is varray(30) of varchar2(200);
  type fileMap is TABLE OF utl_file.file_type INDEX BY VARCHAR2(200);

  comp_types compTypeArr;
  comp_stypes compStypeArr;
  comp_positions compPositionArr;
  compFiles fileArr;
  compFile utl_file.file_type;
  openCompFiles fileMap;
  compFileName varchar2(200);
  i number;

  eventCursor number;
  eventRows  number;
  begin

     --if path_id is null extract for all paths
    path_id_stmt := 'select /* INDEX(' || comp_stat_table || ' ' ||
                    indexName || ') */ ' ||
                    ' distinct path_id from ' ||comp_stat_table || ' ';
    if path_id is not null then
      path_id_stmt := path_id_stmt || ' where path_id = ' || path_id;
    end if;
    path_id_stmt := path_id_stmt || '  order by path_id';

    if lower(comp_stat_table) = 'streams$_pa_show_comp_stat' then
      indexName := 'comp_stat_pkey';
    else
      indexName := lower(comp_stat_table) || '_pk';
    end if;


    begin 
      open path_id_cur for path_id_stmt;
      loop 
        fetch path_id_cur into curr_path_id;
        exit when path_id_cur%notfound;

        -- store all the component information for the path
        comp_types := compTypeArr();
        comp_stypes := compStypeArr();
        comp_positions := compPositionArr();
        compFiles := fileArr();

        comp_stmt := 'select /* INDEX(' || comp_stat_table || ' ' ||
                    indexName || ') */ ' ||
                     'distinct  component_type, sub_component_type ' ||
                     ', position from ' || comp_stat_table ||
                     ' where path_id =' || curr_path_id ||
                     ' order by position';
        open comp_cur for comp_stmt;
        loop
          fetch comp_cur into comp_type, comp_stype, comp_position;
          exit when comp_cur%notfound;
          comp_types.extend(1);
          comp_stypes.extend(1);
          comp_positions.extend(1);
          compFiles.extend(1);
          comp_types(comp_types.last) := comp_type;
          comp_stypes(comp_stypes.last) := comp_stype;
          comp_positions(comp_positions.last) := comp_position;
          
          -- open file for component
          comp_acronym := get_acronym(comp_type,comp_stype);
          if comp_acronym = 'Q' or comp_acronym = 'PS' 
            or comp_acronym = 'PR' then
            compFileName :=  comp_acronym ||'at' || comp_position;
          else
            compFileName := comp_acronym;
          end if;

          compFileName := reportName || '_' || compFileName || '.html';

          -- print the header if not done already 
          if not openCompFiles.exists(compFileName) then
            print_header(directory, compFileName);
            compFile := utl_file.fopen(directory, compFileName, 'A');

            utl_file.put_line(compFile,'<h2><a name="complevelstats">' 
                               || 'COMPONENT LEVEL STATS' 
                               || '</a></h2>');
            utl_file.put_line(compFile, '<b><a name = "'||  fileName || '">'
                              ||comp_acronym ||'</a></b>');
            -- create a table
            utl_file.put_line(compFile, '<table border=2>');
            utl_file.put_line(compFile, '<tr>');
            utl_file.put_line(compFile, '<th>Path Id</th>');
            utl_file.put_line(compFile, '<th>Run Id</th>'); 
            utl_file.put_line(compFile, '<th>Run time</th>');
            utl_file.put_line(compFile, '<th>Throughput</th>');
            
              -- display 'rate 2' as actual statistic
           if comp_acronym = 'CAPTURE' then
             utl_file.put_line(compFile, '<th>Captured/sec</th>');
           elsif comp_acronym = 'Q' then
             utl_file.put_line(compFile, '<th>No of msgs</th>');
           elsif comp_acronym = 'PS' then
             utl_file.put_line(compFile, '<th>bytes/sec</th>');
           elsif comp_acronym = 'APPLY' then
             utl_file.put_line(compFile, '<th>txns applied/sec</th>');
           else
             utl_file.put_line(compFile, '<th>Rate 2</th>');
           end if;

           utl_file.put_line(compFile, '<th>Latency</th>');
           utl_file.put_line(compFile, '<th>% Idle</th>');
           utl_file.put_line(compFile, '<th>% Flwctrl</th>');
           utl_file.put_line(compFile, '<th>% Topevent 1</th>');
           utl_file.put_line(compFile, '<th>Topevent 1</th>');
           utl_file.put_line(compFile, '<th>% Topevent 2</th>');
           utl_file.put_line(compFile, '<th>Topevent 2</th>');
           utl_file.put_line(compFile, '<th>% Topevent 3</th>');
           utl_file.put_line(compFile, '<th>Topevent 3</th>');
           utl_file.put_line(compFile, '</tr>');

           openCompFiles(compFileName) := compFile;

          end if;
          compFiles(compFiles.last) := compFileName;

        end loop;
        close comp_cur;
               

        -- for every run
        for run_id in bgn_run_id..end_run_id loop

          fileName := reportName ||'_' || curr_path_id || '_'
                      || run_id || '.html';
          print_header(directory, fileName);
          file := utl_file.fopen(directory, fileName, 'A');

          utl_file.put_line(file, '<h2><a name = "runlevelstats">' 
                                  || 'RUN LEVEL STATS' ||'</a></h2>');
      
          run_time := get_run_time(comp_stat_table, indexName, run_id,
                                   curr_path_id);

          utl_file.put_line(file, '<b><a name="'|| curr_path_id || '-' 
                               || run_id ||'">'|| 'Path: ' || curr_path_id 
                               || ' Run id: ' || run_id  ||
                               ' Run time: ' || run_time || '</a></b>');

          utl_file.put_line(file, '<table border=2>');
          utl_file.put_line(file, '<tr>');
          utl_file.put_line(file, '<th>Component</th>'); 
          utl_file.put_line(file, '<th>Throughput</th>');
          utl_file.put_line(file, '<th>Rate 2</th>');
          utl_file.put_line(file, '<th>Latency</th>');
          utl_file.put_line(file, '<th>% Idle </th>');
          utl_file.put_line(file, '<th>% Flwctrl </th>');
          utl_file.put_line(file, '<th>% Topevent 1</th>');
          utl_file.put_line(file, '<th>Topevent 1</th>');
          utl_file.put_line(file, '<th>% Topevent 2</th>');
          utl_file.put_line(file, '<th>Topevent 2</th>');
          utl_file.put_line(file, '<th>% Topevent 3</th>');
          utl_file.put_line(file, '<th>Topevent 3</th>');
          utl_file.put_line(file, '</tr>');


         -- get all the components out there
         for i in comp_types.first .. comp_types.last 
         loop
           comp_type := comp_types(i);
           comp_stype :=  comp_stypes(i);
           comp_position := comp_positions(i);
       
           comp_acronym := get_acronym(comp_type,comp_stype);
           compFile := openCompFiles(compFiles(i));

           -- for every such component extract the data
           utl_file.put_line(file, '<tr>');
           utl_file.put_line(compFile, '<tr onclick="DoNav(''' || reportName 
                          || '_' || curr_path_id || '_' || run_id 
                          ||'.html'');" onmouseout="ChangeColor(this,false);"' 
                          || ' onmouseover="ChangeColor(this,true);">');


           -- comp level : path_id, run_id , run_time
           utl_file.put_line(compFile, '<td>' || curr_path_id || '</td>');
           utl_file.put_line(compFile, '<td>' || run_id || '</td>');
           utl_file.put_line(compFile, '<td>' || run_time || '</td>');

           -- populate component name
           if comp_acronym != 'APS' and comp_acronym != 'LMP' then
             if comp_acronym = 'Q' or comp_acronym = 'PS' or
                comp_acronym = 'PR' then
               utl_file.put_line(file, '<td><a href="' || reportName || '_'
                               || comp_acronym ||'at'|| 
                               comp_position||'.html">' ||
                               comp_acronym ||'</a>' || '</td>');
             else
               utl_file.put_line(file, '<td><a href="' || reportName || '_' 
                               || comp_acronym ||'.html">' ||
                              comp_acronym ||'</a>' || '</td>');
             end if;
           else
             stat := get_statistic_by_position(
                          comp_stat_table,'S4', curr_path_id, run_id,
                          comp_position, indexName);
              utl_file.put_line(file, '<td><a href="'|| reportName || '_' 
                            || comp_acronym || '.html">' ||
                            comp_acronym || '(' || stat.stat_value || ')</a>' 
                            || '</td>');
           end if;

           -- populate throughput
           if comp_acronym = 'CAPTURE' then
             -- for capture display enqueued/sec
             stat_alias := 'S2';
           else
             stat_alias := 'S1';
           end if;
          
          stat := get_statistic_by_position(
                          comp_stat_table,stat_alias, curr_path_id, run_id,
                          comp_position, indexName);
         
           if stat.stat_name is not null then
             utl_file.put_line(file, '<td>' || 
                         to_char(stat.stat_value, 'FM99999999') || '</td>');
             utl_file.put_line(compFile, '<td>' || 
                         to_char(stat.stat_value, 'FM99999999') || '</td>');
           else
             utl_file.put_line(file, '<td>&nbsp' || '</td>');
             utl_file.put_line(compFile, '<td>&nbsp' || '</td>');
           end if;

           -- populate rate2 
          if comp_acronym = 'CAPTURE' then
             -- for capture display captured/sec
             stat_alias := 'S1';
           elsif comp_acronym = 'Q' then
             stat_alias := 'S3';
           else
             stat_alias := 'S2';
           end if;
          
           stat := get_statistic_by_position(
                          comp_stat_table,stat_alias, curr_path_id, run_id,
                          comp_position, indexName);
         
           if stat.stat_name is not null then
             utl_file.put_line(file, '<td>' || 
                         to_char(stat.stat_value, 'FM99999999') || '</td>');
             utl_file.put_line(compFile, '<td>' || 
                         to_char(stat.stat_value, 'FM99999999') || '</td>');
           else
             utl_file.put_line(file, '<td>&nbsp' || '</td>');
             utl_file.put_line(compFile, '<td>&nbsp' || '</td>');
           end if;



           -- populate latency
           if comp_acronym != 'Q' then 
             -- for Queues, S3 mean something else
             stat := get_statistic_by_position(
                            comp_stat_table,'S3', curr_path_id, run_id,
                            comp_position, indexName);
         
             if stat.stat_name is not null then
               utl_file.put_line(file, '<td>' || 
                           to_char(stat.stat_value, 'FM99999999') || '</td>');
               utl_file.put_line(compFile, '<td>' || 
                           to_char(stat.stat_value, 'FM99999999') || '</td>');
             else
               utl_file.put_line(file, '<td>&nbsp' || '</td>');
               utl_file.put_line(compFile, '<td>&nbsp' || '</td>');
             end if;
           else
              utl_file.put_line(file, '<td>&nbsp' || '</td>');
              utl_file.put_line(compFile, '<td>&nbsp' || '</td>');
           end if;


           -- populate idle %
           stat := get_statistic_by_position(
                          comp_stat_table,'S5', curr_path_id, run_id,
                          comp_position, indexName);
         
           if stat.stat_name is not null then
             utl_file.put_line(file, '<td>' || 
                         to_char(stat.stat_value, 'FM99999999D9') || '</td>');
             utl_file.put_line(compFile, '<td>' || 
                         to_char(stat.stat_value, 'FM99999999D9') || '</td>');
           else
             utl_file.put_line(file, '<td>&nbsp' || '</td>');
             utl_file.put_line(compFile, '<td>&nbsp' || '</td>');
           end if;
           -- populate flwctrl%
           stat := get_statistic_by_position(
                          comp_stat_table,'S6', curr_path_id, run_id,
                          comp_position, indexName);
           if stat.stat_name is not null then
             utl_file.put_line(file, '<td>' || 
                         to_char(stat.stat_value, 'FM99999999D9') || '</td>');
             utl_file.put_line(compFile, '<td>' || 
                         to_char(stat.stat_value, 'FM99999999D9') || '</td>');
           else
             utl_file.put_line(file, '<td>&nbsp' || '</td>');
             utl_file.put_line(compFile, '<td>&nbsp' || '</td>');
           end if;
          
           -- populate topevents%,topevents
           event_stmt := 'select /* INDEX(' || comp_stat_table || ' ' ||
                         indexName || ') */ ' || 
                         ' statistic_name, statistic_value' || 
                         ' from ' ||
                          comp_stat_table || 
                         ' where path_id = :curr_path_id ' ||
                         ' and  statistic_alias = ''S7'' ' ||
                         ' and position = :comp_position ' ||
                         ' and advisor_run_id = :run_id ' ||
                         ' and component_type = :comp_type ' ||
                         case when comp_acronym = 'PS' or comp_acronym ='PR'
                                    or comp_stype is null
                               then ' and sub_component_type is null'
                               else ' and sub_component_type = :comp_stype '
                         end ||
                         ' and session_id is null  ' || 
                         ' and session_serial# is null ' ||
                         ' order by statistic_value desc';
           eventCursor := dbms_sql.open_cursor;
           dbms_sql.parse(eventCursor, event_stmt, dbms_sql.native);
           dbms_sql.bind_variable(eventCursor, ':curr_path_id', curr_path_id);
           dbms_sql.bind_variable(eventCursor, ':run_id', run_id);
           dbms_sql.bind_variable(eventCursor, ':comp_position', comp_position);
           dbms_sql.bind_variable(eventCursor, ':comp_type', comp_type);
           if not (comp_acronym = 'PS' or comp_acronym ='PR'
                   or comp_stype is null) then
             dbms_sql.bind_variable(eventCursor, ':comp_stype', comp_stype);
           end if;
           dbms_sql.define_column(eventCursor, 1, event_name, 200);
           dbms_sql.define_column(eventCursor, 2, event_val);
           
           eventRows := dbms_sql.execute(eventCursor);
          
           eventRows := 0;
           -- top events
           while dbms_sql.fetch_rows(eventCursor) > 0 loop
             dbms_sql.column_value(eventCursor, 1, event_name);
             dbms_sql.column_value(eventCursor, 2, event_val);
             if event_val <= 0.0 then
               utl_file.put_line(file, '<td>&nbsp' || '</td>');
               utl_file.put_line(file, '<td>&nbsp' || '</td>');
               utl_file.put_line(compFile, '<td>&nbsp' || '</td>');
               utl_file.put_line(compFile, '<td>&nbsp' || '</td>');
             else
               utl_file.put_line(file, '<td>' || to_char(event_val, 
                                     'FM99999999.99') || '</td>');
               utl_file.put_line(file, '<td>' || event_name || '</td>');
               utl_file.put_line(compFile, '<td>' || to_char(event_val, 
                                     'FM99999999.99') || '</td>');
               utl_file.put_line(compFile, '<td>' || event_name || '</td>');
             end if;

             eventRows := eventRows + 1;
             exit when eventRows = 3;
           end loop;

           -- put in empty rows if we had < 3 events
           while (3 - eventRows) > 0 loop
             utl_file.put_line(file, '<td>&nbsp' || '</td>');
             utl_file.put_line(file, '<td>&nbsp' || '</td>');
             utl_file.put_line(compFile, '<td>&nbsp' || '</td>');
             utl_file.put_line(compFile, '<td>&nbsp' || '</td>');
             eventRows := eventRows + 1;
           end loop;

           dbms_sql.close_cursor(eventCursor);

           utl_file.put_line(file, '</tr>');
           utl_file.put_line(compFile, '</tr>');
           
         end loop;
         utl_file.put_line(file, '</table>');
         utl_file.put_line(file, '<br>');
         utl_file.put_line(file,'<h3><a href="'|| reportName ||'">' 
                               || 'Back to Report Home' 
                               || '</a></h3>');
         utl_file.fclose(file);
         print_footer(directory, fileName);
       end loop;

       -- clear all the component info for the path
       comp_types.delete();
       comp_stypes.delete();
       comp_positions.delete();
       compFiles.delete();
     end loop;
     close path_id_cur;
    end;

    -- print footers for all the open component files
    compFileName := openCompFiles.FIRST;
    while compFileName is not null loop
      compFile := openCompFiles(compFileName);
      utl_file.put_line(compFile, '</table>');
      utl_file.put_line(compFile, '<br>');
      utl_file.put_line(compFile,'<h3><a href="'|| reportName ||'">' 
                               || 'Back to Report Home' 
                               || '</a></h3>');
      utl_file.fclose(compFile);
      print_footer(directory, compFileName);
      compFileName := openCompFiles.next(compFileName);
    end loop;


  end PRINT_RUN_AND_COMP_LEVEL_STATS;

  -----------------------------------------------------------------------------=
  -- PRINT_PATHS
  --   Displays a table of all the paths of interest
  --
  -----------------------------------------------------------------------------=
  procedure PRINT_PATHS(
    directory         in varchar2,
    reportName        in varchar2,
    comp_stat_table   in varchar2 default 'STREAMS$_ADVISOR_COMP_STAT',
    path_id in number default null)
  IS
    type cur_type is ref cursor;
    path_id_cur cur_type;
    query_cur cur_type;
    comp_name varchar2(100);
    comp_type varchar2(100);
    comp_stype varchar2(100);
    comp_acronym varchar2(20);
    comp_position number;
    comp_id number;
    path_id_stmt varchar2(4000) := null;
    query_stmt varchar2(4000)   := null;
    current_path_id STREAMS$_PA_COMPONENT_LINK.path_id%TYPE;
    file  utl_file.file_type;
  begin

    file := utl_file.fopen(directory, reportName, 'A');

    utl_file.put_line(file, '<h2><a name="paths">PATHS</a></h2>');


    path_id_stmt := 'select distinct path_id from ' || comp_stat_table || ' ';
    if path_id is not null then
      path_id_stmt := path_id_stmt || ' where path_id = ' || path_id;
    end if;
    path_id_stmt := path_id_stmt || ' order by path_id';
   
    begin 
      open path_id_cur for path_id_stmt;
      loop 
        fetch path_id_cur into current_path_id;
        exit when path_id_cur%notfound;

        -- print the path id
        utl_file.put_line(file, '<b>Path ' || current_path_id || '</b>');
        utl_file.put_line(file, '<table border=2>');
        utl_file.put_line(file, '<tr>');
        utl_file.put_line(file, '<th>Component</th>'); 
        utl_file.put_line(file, '<th>Name</th>');
        utl_file.put_line(file, '<th>Database</th>');
        utl_file.put_line(file, '</tr>');   
       
        query_stmt := 'select distinct component_name, component_type, ' ||
                      'sub_component_type, position, component_id ' || 
                      ' from ' || comp_stat_table ||' ' || 
                      ' where ' ||
                      ' path_id = ' || current_path_id || 
                      ' order by position';

        open query_cur for query_stmt;
        loop
          fetch query_cur into comp_name, comp_type, comp_stype, 
                               comp_position, comp_id;
          exit when query_cur%notfound;

          comp_acronym := get_acronym(comp_type, comp_stype);
          
          if comp_acronym = 'CAPTURE' or 
             comp_acronym = 'Q' or
             comp_acronym = 'PR' or
             comp_acronym = 'PS' or
             comp_acronym = 'APPLY' or
             comp_acronym = 'EXTRACT' or
             comp_acronym = 'REPLICAT'
          then
            utl_file.put_line(file, '<tr>');
            utl_file.put_line(file, '<td>' || comp_acronym || '</td>');
            utl_file.put_line(file, '<td>' || comp_name || '</td>'); 
            utl_file.put_line(file, '<td>' || get_component_db(comp_id) 
                                  || '</td>');
            utl_file.put_line(file, '</tr>');
          end if;
        end loop;
        close query_cur;
        
        -- close the table 
        utl_file.put_line(file, '</table>');

      end loop;
      close path_id_cur;  
    end;
    utl_file.fclose(file);
  end PRINT_PATHS;
  
  -----------------------------------------------------------------------------=
  -- PRINT_TOC
  --   Prints the TOC for the report
  -----------------------------------------------------------------------------=
  procedure PRINT_TOC(
    directory         in varchar2,
    reportName        in varchar2
  ) IS
    file  utl_file.file_type;
  begin
    file := utl_file.fopen(directory, reportName, 'A');

    utl_file.put_line(file, '<h1>Contents</h1>');
    utl_file.put_line(file, '<h2><a href="#legend">Legend</a></h2>');
    utl_file.put_line(file, '<h2><a href="#eventmetrics">Event Metrics' 
                             || '</a></h2>');
    utl_file.put_line(file, '<h2><a href="#paths">Paths</a></h2>');
    utl_file.put_line(file, '<h2><a href="#pathsummary">Path Summary</a></h2>');
    utl_file.put_line(file, '<h2><a href="#eventsummary">Path Level Event ' 
                          || 'Summary </a></h2>');
    utl_file.put_line(file, '<h2><a href="#ratesummary">Rate Level Stats'
                          || '</a></h2>');
    utl_file.put_line(file, '<br>');

    utl_file.fclose(file);
  end PRINT_TOC;

  -----------------------------------------------------------------------------=
  -- PRINT_LEGEND
  --   Prints the legend for the interpreting the various columns in the report
  -----------------------------------------------------------------------------=
  procedure PRINT_LEGEND(
    directory         in varchar2,
    reportName        in varchar2
  ) IS
    file  utl_file.file_type;
  begin

    file := utl_file.fopen(directory, reportName, 'A');

    utl_file.put_line(file, '<h2><a name="legend">Legend</a></h2>');
    utl_file.put_line(file, '<table border=2>');
   
    -- column headers
    utl_file.put_line(file, '<tr>');
     utl_file.put_line(file, '<th>Acronym</th>'); 
     utl_file.put_line(file, '<th>Component</th>');
     utl_file.put_line(file, '<th>Throughput</th>');  
     utl_file.put_line(file, '<th>Rate 2</th>');
    utl_file.put_line(file, '</tr>');

    -- CAP
    utl_file.put_line(file, '<tr>'); 
    utl_file.put_line(file, '<td>CAPTURE' || '</td>');
    utl_file.put_line(file, '<td>Capture Component' || '</td>');
    utl_file.put_line(file, '<td>msgs enqueued/sec' || '</td>');
    utl_file.put_line(file, '<td>msgs captured/sec' || '</td>');
    utl_file.put_line(file, '</tr>');

    -- LMR
    utl_file.put_line(file, '<tr>');
    utl_file.put_line(file, '<td>LMR' || '</td>');
    utl_file.put_line(file, '<td>Log Miner Reader' || '</td>');
    utl_file.put_line(file, '<td>n/a' || '</td>');
    utl_file.put_line(file, '<td>n/a' || '</td>');
    utl_file.put_line(file, '</tr>');

    -- LMP
    utl_file.put_line(file, '<tr>');
    utl_file.put_line(file, '<td>LMP' || '</td>');
    utl_file.put_line(file, '<td>Log Miner Preparer' || '</td>');
    utl_file.put_line(file, '<td>n/a' || '</td>');
    utl_file.put_line(file, '<td>n/a' || '</td>');
    utl_file.put_line(file, '</tr>');

    -- LMB
    utl_file.put_line(file, '<tr>');
    utl_file.put_line(file, '<td>LMB' || '</td>');
    utl_file.put_line(file, '<td>Log Miner Builder' || '</td>');
    utl_file.put_line(file, '<td>n/a' || '</td>');
    utl_file.put_line(file, '<td>n/a' || '</td>');
    utl_file.put_line(file, '</tr>');

    -- CP
    utl_file.put_line(file, '<tr>'); 
    utl_file.put_line(file, '<td>CP' || '</td>');
    utl_file.put_line(file, '<td>Capture Process' || '</td>');
    utl_file.put_line(file, '<td>n/a' || '</td>');
    utl_file.put_line(file, '<td>n/a' || '</td>');
    utl_file.put_line(file, '</tr>');
   
    -- Q
    utl_file.put_line(file, '<tr>');
    utl_file.put_line(file, '<td>Q' || '</td>');
    utl_file.put_line(file, '<td>Queue' || '</td>');
    utl_file.put_line(file, '<td>enqueued/sec' || '</td>');
    utl_file.put_line(file, '<td>no of msgs in queue' || '</td>');
    utl_file.put_line(file, '</tr>');

    -- PS
    utl_file.put_line(file, '<tr>'); 
    utl_file.put_line(file, '<td>PS' || '</td>');
    utl_file.put_line(file, '<td>Propagation Sender' || '</td>');
    utl_file.put_line(file, '<td>msgs/sec' || '</td>');
    utl_file.put_line(file, '<td>bytes/sec' || '</td>');
    utl_file.put_line(file, '</tr>');

    -- PR
    utl_file.put_line(file, '<tr>');
    utl_file.put_line(file, '<td>PR' || '</td>');
    utl_file.put_line(file, '<td>Propagation Receiver' || '</td>');
    utl_file.put_line(file, '<td>n/a' || '</td>');
    utl_file.put_line(file, '<td>n/a' || '</td>');
    utl_file.put_line(file, '</tr>');

    -- APPLY
    utl_file.put_line(file, '<tr>');
    utl_file.put_line(file, '<td>APPLY</td>'); 
    utl_file.put_line(file, '<td>Apply component' || '</td>'); 
    utl_file.put_line(file, '<td>msgs applied/sec' || '</td>');
    utl_file.put_line(file, '<td>txns applied/sec' || '</td>');
    utl_file.put_line(file, '</tr>');

    -- APR
    utl_file.put_line(file, '<tr>');
    utl_file.put_line(file, '<td>APR' || '</td>');
    utl_file.put_line(file, '<td>Apply Reader' || '</td>');
    utl_file.put_line(file, '<td>n/a' || '</td>');
    utl_file.put_line(file, '<td>n/a' || '</td>');
    utl_file.put_line(file, '</tr>');
   
    -- APC
    utl_file.put_line(file, '<tr>');
    utl_file.put_line(file, '<td>APC' || '</td>');
    utl_file.put_line(file, '<td>Apply Coordinator' || '</td>');
    utl_file.put_line(file, '<td>n/a' || '</td>');
    utl_file.put_line(file, '<td>n/a' || '</td>');
    utl_file.put_line(file, '</tr>');
   
    -- APS
    utl_file.put_line(file, '<tr>');
    utl_file.put_line(file, '<td>APS' || '</td>'); 
    utl_file.put_line(file, '<td>Apply Slave' || '</td>'); 
    utl_file.put_line(file, '<td>n/a' || '</td>');
    utl_file.put_line(file, '<td>n/a' || '</td>');
    utl_file.put_line(file, '</tr>');

    -- EXTRACT
    utl_file.put_line(file, '<tr>');
    utl_file.put_line(file, '<td>Extract</td>');
    utl_file.put_line(file, '<td>Extract component' || '</td>');
    utl_file.put_line(file, '<td>msgs sent/sec' || '</td>');
    utl_file.put_line(file, '<td>bytes sent/sec' || '</td>');
    utl_file.put_line(file, '</tr>');

    -- REPLICAT
    utl_file.put_line(file, '<tr>');
    utl_file.put_line(file, '<td>Replicat</td>');
    utl_file.put_line(file, '<td>Replicat component' || '</td>');
    utl_file.put_line(file, '<td>msgs recd/sec' || '</td>');
    utl_file.put_line(file, '<td>byte recd/sec' || '</td>');
    utl_file.put_line(file, '</tr>');

    utl_file.put_line(file, '</table>');

    utl_file.put_line(file, '<h2><a name="eventmetrics">Event Metrics</a>'
                             ||'</h2>');
    utl_file.put_line(file, '<table border=2>'); 

    -- column headers
    utl_file.put_line(file, '<tr>');
    utl_file.put_line(file, '<th>Metric</th>'); 
    utl_file.put_line(file, '<th>Description</th>');
    utl_file.put_line(file, '</tr>');

    --IDLE %
    utl_file.put_line(file, '<tr>');
    utl_file.put_line(file, '<td>IDLE%' || '</td>'); 
    utl_file.put_line(file, '<td>Percent of time in the run,' 
                         || ' spent waiting on upstream ' || 
                         'component' || '</td>'); 
    utl_file.put_line(file, '</tr>');

    --FLWCTRL %
    utl_file.put_line(file, '<tr>');
    utl_file.put_line(file, '<td>FLWCTRL%</td>'); 
    utl_file.put_line(file, '<td>Percent of time in the run, ' 
                         || 'spent waiting on downstream ' ||
                         'component' || '</td>'); 
    utl_file.put_line(file, '</tr>');

    --TOPEVENT
    utl_file.put_line(file, '<tr>');
    utl_file.put_line(file, '<td>TOPEVENT</td>'); 
    utl_file.put_line(file, '<td>Non-idle,Non-flwctrl Event which occupies ' 
                         || 'most of ' || 
                         'run time' || '</td>'); 
    utl_file.put_line(file, '</tr>');
   
    --TOPEVENT%
    utl_file.put_line(file, '<tr>');
    utl_file.put_line(file, '<td>TOPEVENT%' || '</td>'); 
    utl_file.put_line(file, '<td>Percent of time in the run, spent on Topevent' 
                         || '</td>'); 
    utl_file.put_line(file, '</tr>');

    utl_file.put_line(file, '</table>');
    utl_file.fclose(file);
  end PRINT_LEGEND;

  ----------------------------------------------------------------------------=
  -- SHOW_STATS_HTML
  --    generates a html report of the streams performance statistics 
  --    collected using collect_stats
  --
  -- Parameters :
  --   directory       :   directory object name to place the html report
  --   reportName      :   name of the report file to be generated
  --   comp_stat_table :   the comp_stat_tbl used in the previous call to
  --                       collect_stats
  --   path_id         :   path for which statistics needs to be generated
  --   bgn_run_id      :   start run id to generate statistics
  --   end_run_id      :   end run id to generate statistics
  --   detailed        :   TRUE generates run level/ component level 
  --                       statistics also
  --   Print statistics for a stream path.
  ----------------------------------------------------------------------------=
  procedure SHOW_STATS_HTML(
    directory         in varchar2,
    reportName        in varchar2 default 'SPADVREPORT.HTML',
    comp_stat_table   in varchar2 default 'STREAMS$_ADVISOR_COMP_STAT',
    path_id           in number   default null,  -- show all stream paths
    bgn_run_id        in number   default -1,    -- show the last 10 runs
    end_run_id        in number   default -10,
    detailed          in boolean  default TRUE
  ) as
    type cur_type is ref cursor;
    latest_run_id     number default 0;
    actual_bgn_run_id number default 0;
    actual_end_run_id number default 0;
    comp_stat_tbl varchar2(30);
    stmt          varchar2(4000);
    dir           varchar2(255);
    cur cur_type;
  begin
   
    -- check the existence of directory
    dir := UPPER(directory);
    if not check_report_directory(dir) then
      raise_application_error(-20100, 'Invalid Report Directory');
    end if;
 
    -- check existence of table etc
    comp_stat_tbl := CHECK_SPADV_COMP_STAT(comp_stat_table);
    
    -- Check arguments
    if (path_id is not null and path_id < 1) then
      raise_application_error(-20100, 'Invalid path_id');
    end if;
    if (bgn_run_id is null or bgn_run_id = 0) then
      raise_application_error(-20100, 'Invalid bgn_run_id');
    end if;
    if (end_run_id is null or end_run_id = 0) then
      raise_application_error(-20100, 'Invalid end_run_id');
    end if;

    -- Start with the first run (1)
    if (bgn_run_id > 0 and bgn_run_id > end_run_id) then
      raise_application_error(-20100, 'Invalid end_run_id');
    end if;
    -- Start with the latest run (-1)
    if (bgn_run_id < 0 and bgn_run_id < end_run_id ) then
      raise_application_error(-20100, 'Invalid end_run_id');
    end if;
 
    if bgn_run_id <= 0  then
       stmt := 'select distinct advisor_run_id 
       from  ' || comp_stat_tbl || '
       where advisor_run_time in (
                  select max(advisor_run_time)
                  from ' || comp_stat_tbl ||' )';

       open cur for stmt;
       fetch cur into latest_run_id;
       close cur;
 
       actual_bgn_run_id := latest_run_id +  end_run_id  + 1;
       actual_end_run_id := latest_run_id +  bgn_run_id  + 1;
    else
      actual_bgn_run_id := bgn_run_id;
      actual_end_run_id := end_run_id;
    end if;

    PRINT_HEADER(directory, reportName);
    PRINT_TOC(directory, reportName);
    PRINT_LEGEND(directory, reportName);
    PRINT_PATHS(directory, reportName, comp_stat_tbl, path_id);
    PRINT_PATH_LEVEL_SUMMARY(directory, reportName,
                             comp_stat_tbl, path_id, actual_bgn_run_id, 
                             actual_end_run_id);
    PRINT_EVENT_SUMMARY(directory, reportName, 
                        comp_stat_tbl, path_id, actual_bgn_run_id, 
                        actual_end_run_id);
    PRINT_RATE_LEVEL_STATS(directory, reportName,
                           comp_stat_tbl, path_id, actual_bgn_run_id, 
                           actual_end_run_id);
    if detailed then
      PRINT_RUN_AND_COMP_LEVEL_STATS(directory, reportName,
                                  comp_stat_tbl, path_id, actual_bgn_run_id, 
                                  actual_end_run_id);
    end if;
    PRINT_FOOTER(directory, reportName);     

  end SHOW_STATS_HTML;

------------------------------------------------------------------------------=
end UTL_SPADV;
/
show errors;

-- Fix bug 6343077:
-- Drop utl_spadv package and package body 
-- Raise error when utl_spadv is loaded under SYS or SYSTEM
declare
  current_user  dbms_id;
begin
  current_user := SYS_CONTEXT('USERENV', 'CURRENT_USER');

  if (current_user in ('SYS', 'SYSTEM')) then
     execute immediate 'drop package utl_spadv';

     raise_application_error(-20100,
       'The package UTL_SPADV should be loaded into a '||
       'Streams administration schema: current user is '||
       current_user || '.');
  end if;
end;
/

OHA YOOOO