CLASS SessionTrace USE-WIDGET-POOL:

  /*--- Private Set Properties ---*/
   DEFINE PUBLIC STATIC PROPERTY TracingActive    AS LOGICAL   NO-UNDO GET. PRIVATE SET.
   DEFINE PUBLIC STATIC PROPERTY LastErrorMessage AS CHARACTER NO-UNDO GET. PRIVATE SET.
   
  /*--- Public Set Properties ---*/
   DEFINE PUBLIC STATIC PROPERTY UseProfiler      AS LOGICAL   NO-UNDO GET. 
      SET (INPUT ipUseProfiler AS LOGICAL):
        /*--- We can't reset during a tracing run ---*/
         IF TracingActive = TRUE THEN
           ASSIGN LastErrorMessage = "Cannot change profiler settings during a tracing session".
         ELSE
           ASSIGN UseProfiler = ipUseProfiler.
     END SET.
   
  /*--- Define Streams ---*/
   DEFINE STREAM stFile.

  /*--- Define Variables ---*/
   DEFINE PRIVATE STATIC VARIABLE CurrentSnapShotID     AS INTEGER NO-UNDO.
   DEFINE PRIVATE STATIC VARIABLE ProfilerEnabled       AS LOGICAL NO-UNDO.
   DEFINE PRIVATE STATIC VARIABLE SnapShotTotalTime     AS DECIMAL NO-UNDO.
   DEFINE PRIVATE STATIC VARIABLE SnapShotTotalActivity AS DECIMAL NO-UNDO.
   DEFINE PRIVATE STATIC VARIABLE ProfiledTotalTime     AS DECIMAL NO-UNDO.

  /*--- Define Temp-Tables ----*/
   DEFINE PRIVATE STATIC TEMP-TABLE ttDB NO-UNDO
      FIELD DatabaseName AS CHARACTER
      FIELD ConnectID    AS INTEGER
      FIELD StartingTS   AS INTEGER
      FIELD EndingTS     AS INTEGER
      FIELD StartingIS   AS INTEGER
      FIELD EndingIS     AS INTEGER
     INDEX IdxMain IS UNIQUE DatabaseName.

   DEFINE PRIVATE STATIC TEMP-TABLE ttTable NO-UNDO
      FIELD DatabaseName AS CHARACTER
      FIELD TableNumber   AS INTEGER
      FIELD TableName     AS CHARACTER 
      FIELD Reads         AS DECIMAL
      FIELD Creates       AS DECIMAL
      FIELD Updates       AS DECIMAL
      FIELD Deletes       AS DECIMAL
      FIELD IndexActivity AS DECIMAL
      FIELD TableActivity AS DECIMAL
      FIELD TotalActivity AS DECIMAL
     INDEX IdxMain IS UNIQUE DatabaseName TableNumber
     INDEX IdxName DatabaseName TableName.
   
   DEFINE PRIVATE STATIC TEMP-TABLE ttIndex NO-UNDO
      FIELD DatabaseName  AS CHARACTER
      FIELD TableNumber   AS INTEGER
      FIELD TableName     AS CHARACTER 
      FIELD IndexNumber   AS INTEGER
      FIELD IndexName     AS CHARACTER 
      FIELD Reads         AS DECIMAL
      FIELD Creates       AS DECIMAL
      FIELD Deletes       AS DECIMAL
      FIELD IndexActivity AS DECIMAL
      FIELD IsUnique     AS LOGICAL
      FIELD IsWord       AS LOGICAL
     INDEX IdxMain IS UNIQUE DatabaseName IndexNumber
     INDEX IdxName DatabaseName IndexName
     INDEX IdxTable DatabaseName TableName.

   DEFINE PRIVATE STATIC TEMP-TABLE ttSnapShot NO-UNDO
      FIELD SnapShotID      AS INTEGER
      FIELD SnapShotName    AS CHARACTER
      FIELD StartTime       AS DATETIME
      FIELD EndTime         AS DATETIME
      FIELD ElapsedTime     AS DECIMAL
      FIELD IndexActivity   AS DECIMAL
      FIELD TableActivity   AS DECIMAL
      FIELD TotalActivity   AS DECIMAL
      FIELD ProfiledTime    AS DECIMAL
      FIELD PercentTime     AS DECIMAL
      FIELD PercentActivity AS DECIMAL
     INDEX IdxMain IS UNIQUE SnapShotID.

   DEFINE PRIVATE STATIC TEMP-TABLE ttSnapSource NO-UNDO
      FIELD SnapShotID    AS INTEGER
      FIELD SourceID      AS INTEGER
      FIELD SourceName    AS CHARACTER
      FIELD CallCount     AS INTEGER
     INDEX IdxMain SnapShotID SourceID.

   DEFINE PRIVATE STATIC TEMP-TABLE ttSnapCallTree NO-UNDO
      FIELD SnapShotID       AS INTEGER
      FIELD CallerSourceID   AS INTEGER
      FIELD CallerLineNumber AS INTEGER
      FIELD CalleeSourceID   AS INTEGER
      FIELD CallCount        AS INTEGER
     INDEX IdxMain   SnapShotID CalleeSourceID
     INDEX IdxCaller SnapShotID CallerSourceID.
   
   DEFINE PRIVATE STATIC TEMP-TABLE ttSnapLineSummary NO-UNDO
      FIELD SnapShotID       AS INTEGER
      FIELD SourceID         AS INTEGER
      FIELD LineNumber       AS INTEGER
      FIELD StatementCount   AS INTEGER
      FIELD TotalTime        AS DECIMAL
      FIELD CumTime          AS DECIMAL
      FIELD PercentSession   AS DECIMAL
      FIELD PercentSnapShot  AS DECIMAL
     INDEX IdxMain SnapShotID SourceID LineNumber
     INDEX IdxTime SnapShotID TotalTime DESCENDING.

   DEFINE PRIVATE STATIC TEMP-TABLE ttTableSnapShot NO-UNDO
      FIELD SnapShotID   AS INTEGER
      FIELD DatabaseName AS CHARACTER
      FIELD TableNumber  AS INTEGER
      FIELD Reads        AS DECIMAL
      FIELD Creates      AS DECIMAL
      FIELD Updates      AS DECIMAL
      FIELD Deletes      AS DECIMAL
     INDEX IdxMain IS UNIQUE SnapShotID DatabaseName TableNumber
     INDEX IdxTable DatabaseName TableNumber.
   
   DEFINE PRIVATE STATIC TEMP-TABLE ttIndexSnapShot NO-UNDO
      FIELD SnapShotID   AS INTEGER
      FIELD DatabaseName AS CHARACTER
      FIELD IndexNumber  AS INTEGER
      FIELD Reads        AS DECIMAL
      FIELD Creates      AS DECIMAL
      FIELD Deletes      AS DECIMAL
     INDEX IdxMain IS UNIQUE SnapShotID DatabaseName IndexNumber
     INDEX IdxIndex DatabaseName IndexNumber.

   DEFINE PRIVATE STATIC TEMP-TABLE ttTableStat NO-UNDO
      FIELD SnapShotID      AS INTEGER
      FIELD DatabaseName    AS CHARACTER
      FIELD TableNumber     AS INTEGER
      FIELD TableName       AS CHARACTER
      FIELD Reads           AS DECIMAL
      FIELD Creates         AS DECIMAL
      FIELD Updates         AS DECIMAL
      FIELD Deletes         AS DECIMAL
      FIELD TableActivity   AS DECIMAL
      FIELD IndexActivity   AS DECIMAL
      FIELD TotalActivity   AS DECIMAL
      FIELD PercentSession  AS DECIMAL
      FIELD PercentSnapShot AS DECIMAL
     INDEX IdxMain IS UNIQUE SnapShotID DatabaseName TableNumber.

   DEFINE PRIVATE STATIC TEMP-TABLE ttIndexStat NO-UNDO
      FIELD SnapShotID      AS INTEGER
      FIELD DatabaseName    AS CHARACTER
      FIELD IndexNumber     AS INTEGER
      FIELD IndexName       AS CHARACTER
      FIELD TableNumber     AS INTEGER
      FIELD TableName       AS CHARACTER
      FIELD Reads           AS DECIMAL
      FIELD Creates         AS DECIMAL
      FIELD Deletes         AS DECIMAL
      FIELD IndexActivity   AS DECIMAL
      FIELD PercentSession  AS DECIMAL
      FIELD PercentSnapShot AS DECIMAL
     INDEX IdxMain IS UNIQUE SnapShotID DatabaseName IndexNumber.

  /*--- Define Buffers ---*/
   DEFINE PRIVATE STATIC BUFFER bfTableSnapShot FOR ttTableSnapShot.
   DEFINE PRIVATE STATIC BUFFER bfIndexSnapShot FOR ttIndexSnapShot.
   DEFINE PRIVATE STATIC BUFFER bfLineSummary   FOR ttSnapLineSummary.

/*----------------- Private Methods --------------------------------*/
  
  
  /*--- Method to start with a clean slate for a new tracing session ---*/
   METHOD PRIVATE STATIC VOID CleanSlate ():

     /*--- Reset the variables/properties ---*/
      ASSIGN ProfilerEnabled       = FALSE
             CurrentSnapShotID     = 0
             SnapShotTotalTime     = 0
             SnapShotTotalActivity = 0
             ProfiledTotalTime     = 0.

     /*--- Empty the temp-tables ---*/
      EMPTY TEMP-TABLE ttDB.
      EMPTY TEMP-TABLE ttSnapShot.
      EMPTY TEMP-TABLE ttTable.
      EMPTY TEMP-TABLE ttIndex.
      EMPTY TEMP-TABLE ttTableStat.
      EMPTY TEMP-TABLE ttIndexStat.
      EMPTY TEMP-TABLE ttTableSnapShot.
      EMPTY TEMP-TABLE ttIndexSnapShot.
      EMPTY TEMP-TABLE ttSnapSource.
      EMPTY TEMP-TABLE ttSnapCallTree.
      EMPTY TEMP-TABLE ttSnapLineSummary.


   END METHOD. /*--- CleanSlate ---*/

  /*--- Method that compares snapshots ----*/
   METHOD PRIVATE STATIC VOID CompareSnapshots ():

      /*--- Loop through the databases ----*/
       $db$:
       FOR EACH ttDB:

          /*---- Loop through the first index snapshot to compare against the last ---*/
           $index$:
           FOR EACH ttIndexSnapShot WHERE
                    ttIndexSnapShot.DatabaseName = ttDB.DatabaseName AND
                    ttIndexSnapShot.SnapShotID   = 1:

               FIND bfIndexSnapShot WHERE
                    bfIndexSnapShot.DatabaseName = ttIndexSnapShot.DatabaseName AND
                    bfIndexSnapShot.IndexNumber  = ttIndexSnapShot.IndexNumber  AND
                    bfIndexSnapShot.SnapShotID   = CurrentSnapShotID 
                  NO-ERROR.

              /*--- If the db was disconnected or the numbers match remove the Index ---*/ 
               IF NOT AVAILABLE bfIndexSnapShot OR
                 (bfIndexSnapShot.Reads   = ttIndexSnapShot.Reads   AND
                  bfIndexSnapShot.Creates = ttIndexSnapShot.Creates AND
                  bfIndexSnapShot.Deletes = ttIndexSnapShot.Deletes) THEN DO:

                  FOR EACH bfIndexSnapShot WHERE
                           bfIndexSnapShot.DatabaseName = ttIndexSnapShot.DatabaseName AND
                           bfIndexSnapShot.IndexNumber  = ttIndexSnapShot.IndexNumber:
                      DELETE bfIndexSnapShot.
                  END. /*--- EACH bfIndexSnapShot ---*/

                  NEXT $index$.

               END. /*--- Remove the index ---*/

              /*--- Keep track of the index number so we can get more info ---*/
               CREATE ttIndex.
               ASSIGN ttIndex.DatabaseName  = ttIndexSnapShot.DatabaseName
                      ttIndex.IndexNumber   = ttIndexSnapShot.IndexNumber
                      ttIndex.Reads         = bfIndexSnapShot.Reads   - ttIndexSnapShot.Reads
                      ttIndex.Creates       = bfIndexSnapShot.Creates - ttIndexSnapShot.Creates
                      ttIndex.Deletes       = bfIndexSnapShot.Deletes - ttIndexSnapShot.Deletes
                      ttIndex.IndexActivity = ttIndex.Reads + ttIndex.Creates + ttIndex.Deletes.
               
              /*--- Get the rest of the index info ---*/
               GetIndexInfo(ttIndex.DatabaseName,
                            ttIndex.IndexNumber,
                            ttIndex.IndexName,
                            ttIndex.TableName,
                            ttIndex.TableNumber,
                            ttIndex.IsUnique,
                            ttIndex.IsWord).

              /*-- Create or update the ttTable ---*/
               FIND ttTable WHERE
                    ttTable.DatabaseName = ttIndex.DatabaseName AND
                    ttTable.TableNumber  = ttIndex.TableNumber
                  NO-ERROR.

              /*--- Create it if needed ---*/
               IF NOT AVAILABLE ttTable THEN DO:
                  CREATE ttTable.
                  ASSIGN ttTable.DatabaseName = ttIndex.DatabaseName
                         ttTable.TableNumber  = ttIndex.TableNumber
                         ttTable.TableName    = ttIndex.TableName.
               END.

               ASSIGN ttTable.IndexActivity = ttTable.IndexActivity + ttIndex.IndexActivity.

           END. /*--- $index$ ---*/
          
          /*---- Loop through the first table snapshot to compare against the last ---*/
           $table$:
           FOR EACH ttTableSnapShot WHERE
                    ttTableSnapShot.DatabaseName = ttDB.DatabaseName AND
                    ttTableSnapShot.SnapShotID   = 1:

               FIND bfTableSnapShot WHERE
                    bfTableSnapShot.DatabaseName = ttTableSnapShot.DatabaseName AND
                    bfTableSnapShot.TableNumber  = ttTableSnapShot.TableNumber  AND
                    bfTableSnapShot.SnapShotID   = CurrentSnapShotID 
                  NO-ERROR.

              /*--- If the db was disconnected or the numbers match remove the table ---*/ 
               IF NOT AVAILABLE bfTableSnapShot THEN NEXT.

               IF (bfTableSnapShot.Reads   = ttTableSnapShot.Reads   AND
                   bfTableSnapShot.Creates = ttTableSnapShot.Creates AND
                   bfTableSnapShot.Updates = ttTableSnapShot.Updates AND
                   bfTableSnapShot.Deletes = ttTableSnapShot.Deletes) AND
                  NOT CAN-FIND(FIRST ttIndex WHERE
                                     ttIndex.DatabaseName = ttTableSnapShot.DatabaseName AND
                                     ttIndex.TableNumber  = ttTableSnapShot.TableNumber) THEN DO:

                  FOR EACH bfTableSnapShot WHERE
                           bfTableSnapShot.DatabaseName = ttTableSnapShot.DatabaseName AND
                           bfTableSnapShot.TableNumber  = ttTableSnapShot.TableNumber:
                      DELETE bfTableSnapShot.
                  END. /*--- EACH bfTableSnapShot ---*/

                  NEXT $table$.

               END. /*--- Remove the table ---*/

              /*--- Keep track of the table number so we can get more info ---*/
               FIND ttTable WHERE
                    ttTable.DatabaseName = ttTableSnapShot.DatabaseName AND
                    ttTable.TableNumber  = ttTableSnapShot.TableNumber
                   NO-ERROR.

               IF NOT AVAILABLE ttTable THEN DO:
                  CREATE ttTable.
                  ASSIGN ttTable.DatabaseName  = ttTableSnapShot.DatabaseName
                         ttTable.TableNumber   = ttTableSnapShot.TableNumber
                         ttTable.TableName     = GetTableName(ttTable.DatabaseName,
                                                              ttTable.TableNumber).
               END. /*--- Create ttTable ---*/
               
               ASSIGN ttTable.Reads         = bfTableSnapShot.Reads   - ttTableSnapShot.Reads
                      ttTable.Updates       = bfTableSnapShot.Updates - ttTableSnapShot.Updates
                      ttTable.Creates       = bfTableSnapShot.Creates - ttTableSnapShot.Creates
                      ttTable.Deletes       = bfTableSnapShot.Deletes - ttTableSnapShot.Deletes
                      ttTable.TableActivity = ttTable.Reads + ttTable.Updates + ttTable.Creates + ttTable.Deletes
                      ttTable.TotalActivity = ttTable.TableActivity + ttTable.IndexActivity.
               
           END. /*--- $table$ ---*/

       END. /*--- $db$ ---*/

      /*--- Consolidate the stats ---*/
       ConsolidateIndexStats().
       ConsolidateTableStats().

   END METHOD. /*--- CompareSnapShots ---*/

  /*--- Method to consolidate the snapshots into the indexstats ---*/
   METHOD PRIVATE STATIC VOID ConsolidateIndexStats ():

      /*--- Run through the snapshots ---*/
       FOR EACH ttSnapShot WHERE
                ttSnapShot.SnapShotID < CurrentSnapShotID:

           /*--- Run through the indexes for this snapshot ---*/
            FOR EACH ttDB,
                EACH ttIndexSnapShot WHERE
                     ttIndexSnapShot.DatabaseName = ttDB.DatabaseName AND
                     ttIndexSnapShot.SnapShotID   = ttSnapShot.SnapShotID:

                FIND bfIndexSnapShot WHERE
                     bfIndexSnapShot.DatabaseName = ttIndexSnapShot.DatabaseName   AND
                     bfIndexSnapShot.SnapShotID   = ttIndexSnapShot.SnapShotID + 1 AND
                     bfIndexSnapShot.IndexNumber  = ttIndexSnapShot.IndexNumber   
                   NO-ERROR.

                IF NOT AVAILABLE bfIndexSnapShot THEN NEXT.

               /*--- Skip empty activity ---*/
                IF ttIndexSnapShot.Reads   = bfIndexSnapShot.Reads   AND
                   ttIndexSnapShot.Creates = bfIndexSnapShot.Creates AND      
                   ttIndexSnapShot.Deletes = bfIndexSnapShot.Deletes THEN NEXT.

                FIND ttIndex WHERE
                     ttIndex.DatabaseName = ttIndexSnapShot.DatabaseName AND
                     ttIndex.IndexNumber  = ttIndexSnapShot.IndexNumber  NO-ERROR.

                CREATE ttIndexStat.
                BUFFER-COPY ttIndexSnapShot TO ttIndexStat
                ASSIGN ttIndexStat.Reads         = bfIndexSnapShot.Reads         - ttIndexSnapShot.Reads
                       ttIndexStat.Creates       = bfIndexSnapShot.Creates       - ttIndexSnapShot.Creates
                       ttIndexStat.Deletes       = bfIndexSnapShot.Deletes       - ttIndexSnapShot.Deletes
                       ttIndexStat.IndexActivity = ttIndexStat.Reads + ttIndexStat.Creates + ttIndexStat.Deletes
                       ttIndexStat.IndexName     = ttIndex.IndexName
                       ttIndexStat.TableName     = ttIndex.TableName
                       ttIndexStat.TableNumber   = ttIndex.TableNumber.

                ASSIGN ttSnapShot.IndexActivity = ttSnapShot.IndexActivity + ttIndexStat.IndexActivity.
            END. /*--- EACH ttIndexSnapShot ---*/

       END. /*--- EACH ttSnapShot ----*/

   END METHOD. /*--- ConsolidateIndexStats ---*/

  /*--- Method to consolidate the snapshots into the indexstats ---*/
   METHOD PRIVATE STATIC VOID ConsolidateTableStats ():
      
      /*--- Run through the snapshots ---*/
       FOR EACH ttSnapShot WHERE
                ttSnapShot.SnapShotID < CurrentSnapShotID:

           /*--- Run through the tables for this snapshot ---*/
            FOR EACH ttDB,
                EACH ttTableSnapShot WHERE
                     ttTableSnapShot.DatabaseName = ttDB.DatabaseName AND
                     ttTableSnapShot.SnapShotID   = ttSnapShot.SnapShotID:

                FIND bfTableSnapShot WHERE
                     bfTableSnapShot.DatabaseName = ttTableSnapShot.DatabaseName   AND
                     bfTableSnapShot.SnapShotID   = ttTableSnapShot.SnapShotID + 1 AND
                     bfTableSnapShot.TableNumber  = ttTableSnapShot.TableNumber   
                   NO-ERROR.

                IF NOT AVAILABLE bfTableSnapShot THEN NEXT.
               
               /*--- Skip empty activity ---*/
                IF ttTableSnapShot.Reads   = bfTableSnapShot.Reads   AND
                   ttTableSnapShot.Creates = bfTableSnapShot.Creates AND      
                   ttTableSnapShot.Updates = bfTableSnapShot.Updates AND
                   ttTableSnapShot.Deletes = bfTableSnapShot.Deletes AND
                   NOT CAN-FIND(FIRST ttIndexStat WHERE
                                      ttIndexStat.DatabaseName = ttTableSnapShot.DatabaseName AND
                                      ttIndexStat.SnapShotID   = ttTableSnapShot.SnapShotID   AND
                                      ttIndexStat.TableNumber  = ttTableSnapShot.TableNumber) THEN NEXT.

                FIND ttTable WHERE 
                     ttTable.DatabaseName = ttTableSnapShot.DatabaseName AND
                     ttTable.TableNumber  = ttTableSnapShot.TableNumber  NO-ERROR.

                CREATE ttTableStat.
                BUFFER-COPY ttTableSnapShot TO ttTableStat
                ASSIGN ttTableStat.Reads         = bfTableSnapShot.Reads   - ttTableSnapShot.Reads
                       ttTableStat.Creates       = bfTableSnapShot.Creates - ttTableSnapShot.Creates
                       ttTableStat.Deletes       = bfTableSnapShot.Deletes - ttTableSnapShot.Deletes
                       ttTableStat.Updates       = bfTableSnapShot.Updates - ttTableSnapShot.Updates
                       ttTableStat.TableActivity = ttTableStat.Reads + ttTableStat.Creates + ttTableStat.Deletes + ttTableStat.Updates
                       ttTableStat.TableName     = ttTable.TableName.
                      
               /*--- Add up the index activity ---*/
                FOR EACH ttIndexStat WHERE
                         ttIndexStat.DatabaseName = ttTableStat.DatabaseName AND
                         ttIndexStat.SnapShotID   = ttTableStat.SnapShotID   AND
                         ttIndexStat.TableNumber  = ttTableStat.TableNumber:
                  ASSIGN ttTableStat.IndexActivity = ttTableStat.IndexActivity + ttIndexStat.IndexActivity.
                END.
                
                ASSIGN ttTableStat.TotalActivity = ttTableStat.TableActivity + ttTableStat.IndexActivity
                       ttSnapShot.TableActivity  = ttSnapShot.TableActivity  + ttTableStat.TableActivity
                       ttSnapShot.TotalActivity  = ttSnapShot.TableActivity  + ttSnapShot.IndexActivity
                       SnapShotTotalActivity     = SnapShotTotalActivity     + ttTableStat.TotalActivity.

            END. /*--- EACH ttTableSnapShot ---*/

       END. /*--- EACH ttSnapShot ----*/

   END METHOD. /*--- ConsolidateTableStats ---*/

  /*--- Method that retrieves the current list of connected databases ---*/
   METHOD PRIVATE STATIC VOID GetDatabases ():

     /*--- Local Variables ---*/
      DEFINE VARIABLE DBCounter   AS INTEGER NO-UNDO.

     /*--- Loop through the databases ---*/
      DO DBCounter = 1 TO NUM-DBS:
      
         /*--- Make sure this is a Progress DB ---*/
          IF DBTYPE(DBCounter) <> "PROGRESS" THEN NEXT.

         /*--- Create the ttDb ----*/
          CREATE ttDB.
          ASSIGN ttDB.DatabaseName = LDBNAME(DBCounter)
                 ttDB.ConnectID    = GetConnectionID(ttDB.DatabaseName).

         /*--- Is everything ok? ---*/
          IF ttDB.ConnectID = ? THEN NEXT.

         /*--- Get the _UserTableStat range for this connection ---*/
          GetTableRange(ttDB.DatabaseName,
                        ttDB.ConnectID,
                        ttDB.StartingTS,
                        ttDB.EndingTS).
         
         /*--- Get the _UserIndexStat range for this connection ---*/
          GetIndexRange(ttDB.DatabaseName,
                        ttDB.ConnectID,
                        ttDB.StartingIS,
                        ttDB.EndingIS).

      END. /*--- DO DBCounter ---*/

   END METHOD. /*--- GetDatabases ---*/
  
  /*--- Method to retrieve the current connection id for a database ---*/
   METHOD PRIVATE STATIC INTEGER GetConnectionID (INPUT ipDatabaseName AS CHARACTER):
     
     /*---- Local Variables ---*/
      DEFINE VARIABLE TableBuffer AS HANDLE  NO-UNDO.
      DEFINE VARIABLE FoundRecord AS LOGICAL NO-UNDO.
      DEFINE VARIABLE ConnectID   AS INTEGER NO-UNDO.

     /*---- Get the connection id ----*/
      CREATE BUFFER TableBuffer FOR TABLE(ipDatabaseName + "._MyConnection") NO-ERROR.
      
      ASSIGN FoundRecord = TableBuffer:FIND-FIRST("",NO-LOCK) NO-ERROR.

     /*--- What happened? ---*/
      IF FoundRecord = TRUE THEN
        ASSIGN ConnectID = TableBuffer:BUFFER-FIELD("_MyConn-UserId"):BUFFER-VALUE NO-ERROR.
     
      ELSE 
         ASSIGN ConnectID = ?.

     /*--- Clean up ---*/
      DELETE OBJECT TableBuffer NO-ERROR.
       
      RETURN ConnectID.

   END METHOD. /*---- GetConnectionID ----*/
  
  /*--- Method to get the index info for a single index ---*/
   METHOD PRIVATE STATIC VOID GetIndexInfo (INPUT  ipDatabaseName AS CHARACTER,
                                            INPUT  ipIndexNumber  AS INTEGER,
                                            OUTPUT opIndexName    AS CHARACTER,
                                            OUTPUT opTableName    AS CHARACTER,
                                            OUTPUT opTableNumber  AS INTEGER,
                                            OUTPUT opIsUnique     AS LOGICAL,
                                            OUTPUT opIsWord       AS LOGICAL):

       /*--- Local Variables ---*/
        DEFINE VARIABLE TableBuffer AS HANDLE  NO-UNDO.
        DEFINE VARIABLE FoundRecord AS LOGICAL NO-UNDO.
        DEFINE VARIABLE FileRecid   AS RECID   NO-UNDO.

       /*--- Create the buffer ---*/
        CREATE BUFFER TableBuffer FOR TABLE(ipDatabaseName + "._Index") NO-ERROR.

        ASSIGN FoundRecord = TableBuffer:FIND-FIRST("WHERE " + ipDatabaseName + "._Index._Idx-Num = " +
                                                    STRING(ipIndexNumber),
                                                    NO-LOCK) NO-ERROR.
        IF FoundRecord THEN
          ASSIGN opIndexName = TableBuffer:BUFFER-FIELD("_Index-Name"):BUFFER-VALUE
                 opIsUnique  = TableBuffer:BUFFER-FIELD("_Unique"):BUFFER-VALUE
                 opIsWord    = TableBuffer:BUFFER-FIELD("_Wordidx"):BUFFER-VALUE <> ?
                 FileRecid   = TableBuffer:BUFFER-FIELD("_File-Recid"):BUFFER-VALUE.
       
       /*--- Clean up ---*/
        DELETE OBJECT TableBuffer NO-ERROR.
    
       /*--- Create the buffer for the _file ---*/
        CREATE BUFFER TableBuffer FOR TABLE(ipDatabaseName + "._File") NO-ERROR.

        ASSIGN FoundRecord = TableBuffer:FIND-FIRST("WHERE RECID(" + ipDatabaseName + "._File) = " +
                                                    STRING(FileRecid),
                                                    NO-LOCK) NO-ERROR.
        IF FoundRecord = TRUE THEN
          ASSIGN opTableName   = TableBuffer:BUFFER-FIELD("_File-Name"):BUFFER-VALUE 
                 opTableNumber = TableBuffer:BUFFER-FIELD("_File-Number"):BUFFER-VALUE
              NO-ERROR.

       /*--- Clean up ---*/
        DELETE OBJECT TableBuffer NO-ERROR.

   END METHOD. /*--- GetIndexInfo ---*/

  /*--- Method to get the range of IDs for the _UserIndexStat ----*/
   METHOD PRIVATE STATIC VOID GetIndexRange (INPUT  ipDatabaseName AS CHARACTER,
                                             INPUT  ipConnectionID AS INTEGER,
                                             OUTPUT opStartingTS   AS INTEGER,
                                             OUTPUT opEndingTS     AS INTEGER):

       /*--- Local Variables ----*/
        DEFINE VARIABLE TableBuffer      AS HANDLE  NO-UNDO.
        DEFINE VARIABLE FoundRecord      AS LOGICAL NO-UNDO.
        DEFINE VARIABLE FirstIndexNumber AS INTEGER NO-UNDO.
        DEFINE VARIABLE LastIndexNumber  AS INTEGER NO-UNDO.
        DEFINE VARIABLE IndexRange       AS INTEGER NO-UNDO.

       /*--- Create the buffer ---*/
        CREATE BUFFER TableBuffer FOR TABLE(ipDatabaseName + "._IndexStat") NO-ERROR.
        
       /*--- Get the starting point ---*/
        ASSIGN FoundRecord = TableBuffer:FIND-FIRST("",NO-LOCK) NO-ERROR.

        IF FoundRecord = TRUE THEN
          ASSIGN FirstIndexNumber = TableBuffer:BUFFER-FIELD("_IndexStat-ID"):BUFFER-VALUE NO-ERROR.
       
       /*--- Get the endning point ---*/
        ASSIGN FoundRecord = TableBuffer:FIND-LAST("",NO-LOCK) NO-ERROR.

        IF FoundRecord = TRUE THEN
          ASSIGN LastIndexNumber = TableBuffer:BUFFER-FIELD("_IndexStat-ID"):BUFFER-VALUE NO-ERROR.

       /*--- What is our range? ---*/
        ASSIGN IndexRange = LastIndexNumber - FirstIndexNumber + 1.

       /*--- Do the math on which slots will contain our connection ---*/
        ASSIGN opStartingTS = 1          + (IndexRange * ipConnectionID)
               opEndingTS   = IndexRange + (IndexRange * ipConnectionID).
 
       /*--- Clean up ----*/
        DELETE OBJECT TableBuffer NO-ERROR.

   END METHOD. /*--- GetIndexRange ---*/

  /*--- Method to grab the _UserIndexStat values for a connection ---*/
   METHOD PRIVATE STATIC VOID GetIndexStats (INPUT ipDatabaseName AS CHARACTER,
                                      INPUT ipConnectionID AS INTEGER,
                                      INPUT ipStartingTS   AS INTEGER,
                                      INPUT ipEndingTS     AS INTEGER):

       /*--- Local Variables ---*/
        DEFINE VARIABLE QueryHandle AS HANDLE    NO-UNDO.
        DEFINE VARIABLE TableBuffer AS HANDLE    NO-UNDO.
        DEFINE VARIABLE QueryTable  AS CHARACTER NO-UNDO.
        DEFINE VARIABLE QueryWhere  AS CHARACTER NO-UNDO.

       /*--- Create the buffer ---*/
        ASSIGN QueryTable = ipDatabaseName + "._UserIndexStat".

        CREATE BUFFER TableBuffer FOR TABLE(QueryTable) NO-ERROR.

       /*--- Create the query ----*/
        ASSIGN QueryWhere = SUBSTITUTE("FOR EACH &1 WHERE &1._UserIndexStat-ID >= &2 " +
                                       "AND &1._UserIndexStat-ID <= &3 AND "           +
                                       "&1._UserIndexStat-Conn = &4 NO-LOCK",
                                       QueryTable,
                                       ipStartingTS,
                                       ipEndingTS,
                                       ipConnectionID).

        CREATE QUERY QueryHandle.
        QueryHandle:SET-BUFFERS(TableBuffer) NO-ERROR.
        QueryHandle:FORWARD-ONLY = TRUE.
        QueryHandle:QUERY-PREPARE(QueryWhere) NO-ERROR.
        QueryHandle:QUERY-OPEN() NO-ERROR.

       /*--- Loop through the records ---*/
        $loop$:
        REPEAT:

            QueryHandle:GET-NEXT(NO-LOCK) NO-ERROR.

            IF QueryHandle:QUERY-OFF-END = TRUE THEN LEAVE $loop$.

           /*--- Create and assign the temp-table ---*/
            CREATE ttIndexSnapShot.
            ASSIGN ttIndexSnapShot.DatabaseName = ipDatabaseName
                   ttIndexSnapShot.SnapShotID   = CurrentSnapShotID
                   ttIndexSnapShot.IndexNumber  = TableBuffer:BUFFER-FIELD("_UserIndexStat-Num"):BUFFER-VALUE
                   ttIndexSnapShot.Reads        = TableBuffer:BUFFER-FIELD("_UserIndexStat-Read"):BUFFER-VALUE
                   ttIndexSnapShot.Creates      = TableBuffer:BUFFER-FIELD("_UserIndexStat-Create"):BUFFER-VALUE
                   ttIndexSnapShot.Deletes      = TableBuffer:BUFFER-FIELD("_UserIndexStat-Delete"):BUFFER-VALUE
                NO-ERROR.

        END. /*--- $loop$ ---*/

       /*--- Clean up ----*/
        QueryHandle:QUERY-CLOSE() NO-ERROR.
        DELETE OBJECT QueryHandle NO-ERROR.
        DELETE OBJECT TableBuffer NO-ERROR.

   END METHOD. /*--- GetIndexStats ---*/

  /*--- Method to get the table name ---*/
   METHOD PRIVATE STATIC CHARACTER GetTableName (INPUT  ipDatabaseName AS CHARACTER,
                                          INPUT  ipTableNumber  AS INTEGER):

       /*--- Local Variables ---*/
        DEFINE VARIABLE TableBuffer AS HANDLE    NO-UNDO.
        DEFINE VARIABLE FoundRecord AS LOGICAL   NO-UNDO.
        DEFINE VARIABLE TableName   AS CHARACTER NO-UNDO.

       /*--- Create the buffer for the _file ---*/
        CREATE BUFFER TableBuffer FOR TABLE(ipDatabaseName + "._File") NO-ERROR.

        ASSIGN FoundRecord = TableBuffer:FIND-FIRST("WHERE " + ipDatabaseName + "._File._File-Number = " +
                                                    STRING(ipTableNumber),
                                                    NO-LOCK) NO-ERROR.
        
        IF FoundRecord = TRUE THEN
          ASSIGN TableName = TableBuffer:BUFFER-FIELD("_File-Name"):BUFFER-VALUE NO-ERROR.

       /*--- Clean up ---*/
        DELETE OBJECT TableBuffer NO-ERROR.

        RETURN TableName.


   END METHOD. /*--- GetTableName ---*/

  /*--- Method to get the range of IDs for the _UserTableStat ----*/
   METHOD PRIVATE STATIC VOID GetTableRange (INPUT  ipDatabaseName AS CHARACTER,
                                      INPUT  ipConnectionID AS INTEGER,
                                      OUTPUT opStartingTS   AS INTEGER,
                                      OUTPUT opEndingTS     AS INTEGER):

       /*--- Local Variables ----*/
        DEFINE VARIABLE TableBuffer      AS HANDLE  NO-UNDO.
        DEFINE VARIABLE FoundRecord      AS LOGICAL NO-UNDO.
        DEFINE VARIABLE FirstTableNumber AS INTEGER NO-UNDO.
        DEFINE VARIABLE LastTableNumber  AS INTEGER NO-UNDO.
        DEFINE VARIABLE TableRange       AS INTEGER NO-UNDO.

       /*--- Create the buffer ---*/
        CREATE BUFFER TableBuffer FOR TABLE(ipDatabaseName + "._TableStat") NO-ERROR.
        
       /*--- Get the starting point ---*/
        ASSIGN FoundRecord = TableBuffer:FIND-FIRST("",NO-LOCK) NO-ERROR.

        IF FoundRecord = TRUE THEN
          ASSIGN FirstTableNumber = TableBuffer:BUFFER-FIELD("_TableStat-ID"):BUFFER-VALUE NO-ERROR.
       
       /*--- Get the endning point ---*/
        ASSIGN FoundRecord = TableBuffer:FIND-LAST("",NO-LOCK) NO-ERROR.

        IF FoundRecord = TRUE THEN
          ASSIGN LastTableNumber = TableBuffer:BUFFER-FIELD("_TableStat-ID"):BUFFER-VALUE NO-ERROR.

       /*--- What is our range? ---*/
        ASSIGN TableRange = LastTableNumber - FirstTableNumber + 1.

       /*--- Do the math on which slots will contain our connection ---*/
        ASSIGN opStartingTS = 1          + (TableRange * ipConnectionID)
               opEndingTS   = TableRange + (TableRange * ipConnectionID).
 
       /*--- Clean up ----*/
        DELETE OBJECT TableBuffer NO-ERROR.

   END METHOD. /*--- GetTableRange ---*/

  /*--- Method to grab the _UserTableStat values for a connection ---*/
   METHOD PRIVATE STATIC VOID GetTableStats (INPUT ipDatabaseName AS CHARACTER,
                                      INPUT ipConnectionID AS INTEGER,
                                      INPUT ipStartingTS   AS INTEGER,
                                      INPUT ipEndingTS     AS INTEGER):

       /*--- Local Variables ---*/
        DEFINE VARIABLE QueryHandle AS HANDLE    NO-UNDO.
        DEFINE VARIABLE TableBuffer AS HANDLE    NO-UNDO.
        DEFINE VARIABLE QueryTable  AS CHARACTER NO-UNDO.
        DEFINE VARIABLE QueryWhere  AS CHARACTER NO-UNDO.

       /*--- Create the buffer ---*/
        ASSIGN QueryTable = ipDatabaseName + "._UserTableStat".

        CREATE BUFFER TableBuffer FOR TABLE(QueryTable) NO-ERROR.

       /*--- Create the query ----*/
        ASSIGN QueryWhere = SUBSTITUTE("FOR EACH &1 WHERE &1._UserTableStat-ID >= &2 " +
                                       "AND &1._UserTableStat-ID <= &3 AND "           +
                                       "&1._UserTableStat-Conn = &4 NO-LOCK",
                                       QueryTable,
                                       ipStartingTS,
                                       ipEndingTS,
                                       ipConnectionID).

        CREATE QUERY QueryHandle.
        QueryHandle:SET-BUFFERS(TableBuffer) NO-ERROR.
        QueryHandle:FORWARD-ONLY = TRUE.
        QueryHandle:QUERY-PREPARE(QueryWhere) NO-ERROR.
        QueryHandle:QUERY-OPEN() NO-ERROR.

       /*--- Loop through the records ---*/
        $loop$:
        REPEAT:

            QueryHandle:GET-NEXT(NO-LOCK) NO-ERROR.

            IF QueryHandle:QUERY-OFF-END = TRUE THEN LEAVE $loop$.

           /*--- Create and assign the temp-table ---*/
            CREATE ttTableSnapShot.
            ASSIGN ttTableSnapShot.DatabaseName = ipDatabaseName
                   ttTableSnapShot.SnapShotID   = CurrentSnapShotID
                   ttTableSnapShot.TableNumber  = TableBuffer:BUFFER-FIELD("_UserTableStat-Num"):BUFFER-VALUE
                   ttTableSnapShot.Reads        = TableBuffer:BUFFER-FIELD("_UserTableStat-Read"):BUFFER-VALUE
                   ttTableSnapShot.Creates      = TableBuffer:BUFFER-FIELD("_UserTableStat-Create"):BUFFER-VALUE
                   ttTableSnapShot.Updates      = TableBuffer:BUFFER-FIELD("_UserTableStat-Update"):BUFFER-VALUE
                   ttTableSnapShot.Deletes      = TableBuffer:BUFFER-FIELD("_UserTableStat-Delete"):BUFFER-VALUE
                NO-ERROR.

        END. /*--- $loop$ ---*/

       /*--- Clean up ----*/
        QueryHandle:QUERY-CLOSE() NO-ERROR.
        DELETE OBJECT QueryHandle NO-ERROR.
        DELETE OBJECT TableBuffer NO-ERROR.

   END METHOD. /*--- GetTableStats ---*/

  /*---- Method to load profiler data ---*/ 
   METHOD PRIVATE STATIC VOID LoadProfiler (INPUT ipSnapShotID AS INTEGER,
                                            INPUT ipFileName   AS CHARACTER):

    /*--- Local Variables ---*/
     DEFINE VARIABLE ProfilerVersion AS INTEGER   NO-UNDO.
     DEFINE VARIABLE NumericFormat   AS CHARACTER NO-UNDO.

    /*--- Find the snapshot ---*/
     FIND ttSnapShot WHERE
          ttSnapShot.SnapShotID = ipSnapShotID NO-ERROR.

    /*--- Save the current numeric format ---*/
     IF SESSION:NUMERIC-FORMAT <> "AMERICAN" THEN DO:
        ASSIGN NumericFormat = SESSION:NUMERIC-FORMAT.
        SESSION:NUMERIC-FORMAT = "AMERICAN".
     END.

    /*--- Open the file ---*/ 
     INPUT STREAM stFile FROM VALUE(ipFileName) NO-ECHO.

    /*--- Read the session ---*/
     $session$:
     REPEAT ON ENDKEY UNDO, LEAVE $session$:
       IMPORT STREAM stFile ProfilerVersion.
     END. /*--- $session$ ---*/

    /*--- Read the Source Files ---*/
     $source$:
     REPEAT ON ENDKEY UNDO, LEAVE $source$:
       CREATE ttSnapSource.
       ASSIGN ttSnapSource.SnapShotID = ipSnapShotID.
       IMPORT STREAM stFile ttSnapSource.SourceID ttSnapSource.SourceName.
     END.
     DELETE ttSnapSource.

    /*--- Read the calltree data ---*/
     $calltree$:
     REPEAT ON ENDKEY UNDO, LEAVE $calltree$:
         CREATE ttSnapCallTree.
         ASSIGN ttSnapCallTree.SnapShotID = ipSnapShotID.
         IMPORT STREAM stFile ttSnapCallTree.CallerSourceID 
                              ttSnapCallTree.CallerLineNumber
                              ttSnapCallTree.CalleeSourceID
                              ttSnapCallTree.CallCount.
     END.
     DELETE ttSnapCallTree.

    /*--- Read the summary data ---*/
     $summary$:
     REPEAT ON ENDKEY UNDO, LEAVE $summary$:
         CREATE ttSnapLineSummary.
         ASSIGN ttSnapLineSummary.SnapShotID = ipSnapShotID.
         IMPORT STREAM stFile ttSnapLineSummary.SourceID
                              ttSnapLineSummary.LineNumber
                              ttSnapLineSummary.StatementCount
                              ttSnapLineSummary.TotalTime
                              ttSnapLineSummary.CumTime.

         ASSIGN ttSnapShot.ProfiledTime = ttSnapShot.ProfiledTime + ttSnapLineSummary.TotalTIme.
     END.
     DELETE ttSnapLineSummary.

    /*--- Track and back out all of our class activity ---*/
     FOR EACH ttSnapSource WHERE
              ttSnapSource.SnapShotID = ttSnapShot.SnapShotID AND
              ttSnapSource.SourceName MATCHES "* SessionTrace",
         EACH ttSnapLineSummary WHERE
              ttSnapLineSummary.SnapShotID = ttSnapShot.SnapShotID AND
              ttSnapLineSummary.SourceID   = ttSnapSource.SourceID:
        
        /*--- Back out the total ---*/
         ASSIGN ttSnapShot.ProfiledTime = ttSnapShot.ProfiledTime - ttSnapLineSummary.TotalTIme.

        /*--- Delete the line ---*/
         DELETE ttSnapLineSummary.
         
     END. /*---- EACH SessionTrace -----*/


    /*--- Close the file ---*/
     INPUT STREAM stFile CLOSE.

    /*--- Delete the file ---*/
     OS-DELETE VALUE(ipFileName) NO-ERROR.

    /*--- Reset the numeric format? ---*/
     IF NumericFormat <> "" THEN
       SESSION:NUMERIC-FORMAT = NumericFormat.
 
   END METHOD. /*--- LoadProfiler ---*/

/*-------------------- Public Methods ----------------------------------------------*/

  /*--- Method to start tracing a session ----*/ 
   METHOD PUBLIC STATIC LOGICAL StartTracing():

     /*--- If tracing is already active we can't start it again ---*/
      IF TracingActive = TRUE THEN DO:
         ASSIGN LastErrorMessage = "Tracing is already active.".
         RETURN FALSE.
      END. /*--- Tracing is already active ---*/

     /*--- We have started tracing the session ---*/ 
      ASSIGN TracingActive = TRUE.

     /*--- Make sure we start with a clean slate ---*/
      CleanSlate().

     /*--- Get our databsae information ---*/
      GetDatabases().

     /*--- Take the first snapshot ---*/  
      StartSnapshot (INPUT "Session Tracing").
    
   END METHOD. /*--- StartTracing ---*/
  
  /*--- Method to stop tracing a sessionDrwtsn32 ---*/
   METHOD PUBLIC STATIC LOGICAL StopTracing ():
     
     /*--- If tracing is already inactive we can't stop it again ---*/
      IF TracingActive = FALSE THEN DO:
         ASSIGN LastErrorMessage = "Tracing is already inactive.".
         RETURN FALSE.
      END. /*--- Tracing is already inactive ---*/

     /*--- Take one last snapshot ---*/
      StartSnapshot(INPUT "*** End ***").

     /*--- Accumulate a few things ---*/
      FOR EACH ttSnapShot WHERE 
               ttSnapShot.SnapShotID < CurrentSnapShotID:
         
         ASSIGN SnapShotTotalTime = SnapShotTotalTime  + ttSnapShot.ElapsedTime
                ProfiledTotalTime = ProfiledTotalTime  + ttSnapShot.ProfiledTime.

      END. /*--- Each ttSnapShot ---*/
     
     /*--- Compare the snapshots to each other ---*/
      CompareSnapShots().
     
     /*--- get rid of the placeholder last snapshot ---*/
      FIND ttSnapShot WHERE
           ttSnapShot.SnapShotID = CurrentSnapShotID NO-ERROR.

      DELETE ttSnapShot.

     /*--- Set the lower level percentages ---*/
      FOR EACH ttSnapShot:

          ASSIGN ttSnapShot.PercentTime     = MIN((ttSnapShot.ElapsedTime / SnapShotTotalTime) * 100,100)
                 ttSnapShot.PercentActivity = MIN((ttSnapShot.TotalActivity / SnapShotTotalActivity) * 100,100).
          
         FOR EACH ttTableStat WHERE
                   ttTableStat.SnapShotID = ttSnapShot.SnapShotID:
             ASSIGN ttTableStat.PercentSession  = MIN((ttTableStat.TotalActivity / SnapShotTotalActivity) * 100,100)
                    ttTableStat.PercentSnapShot = MIN((ttTableStat.TotalActivity / ttSnapShot.TotalActivity) * 100,100).
          END. /*--- EACH ttTableStat ---*/
          
          FOR EACH ttIndexStat WHERE
                   ttIndexStat.SnapShotID = ttSnapShot.SnapShotID:
             ASSIGN ttIndexStat.PercentSession  = MIN((ttIndexStat.IndexActivity / SnapShotTotalActivity) * 100,100)
                    ttIndexStat.PercentSnapShot = MIN((ttIndexStat.IndexActivity / ttSnapShot.TotalActivity) * 100,100).
          END. /*--- EACH ttIndexStat ---*/

          FOR EACH ttSnapLineSummary WHERE
                   ttSnapLineSummary.SnapShotID = ttSnapShot.SnapShotID:
             ASSIGN ttSnapLineSummary.PercentSession  = MIN((ttSnapLineSummary.TotalTime / ProfiledTotalTime) * 100,100)
                    ttSnapLineSummary.PercentSnapShot = MIN((ttSnapLineSummary.TotalTime / ttSnapShot.ProfiledTime) * 100,100).
          END. /*--- EACH ttSnapLineSummary ---*/
                  

      END. /*--- EACH ttSnapShot ---*/

     /*--- We have started tracing the session ---*/ 
      ASSIGN TracingActive = FALSE.

      RETURN TRUE.

   END METHOD. /*--- StopTracing ---*/
  
  /*--- Default Method to take a snapshot ---*/
   METHOD PUBLIC STATIC LOGICAL StartSnapshot (INPUT  ipSnapShotName AS CHARACTER):
     
     /*--- If tracing isn't active return an error ---*/
      IF TracingActive = FALSE THEN DO:
        ASSIGN LastErrorMessage = "StartTracing has not been run.".
        RETURN FALSE.
      END. /*--- Tracing isn't active ---*/
      
     /*--- Stop profiling? ---*/
      IF UseProfiler = TRUE AND PROFILER:ENABLED = TRUE THEN DO:
         /*--- Turn off profiling ---*/
         PROFILER:PROFILING = FALSE.
         PROFILER:ENABLED   = FALSE.

        /*--- Load the profiler file ---*/
         LoadProfiler(CurrentSnapShotID,PROFILER:FILE-NAME).

      END.

     /*--- Mark the old snapshot as completed? ---*/
      FIND ttSnapShot WHERE
           ttSnapShot.SnapShotID = CurrentSnapShotID NO-ERROR.

      IF AVAILABLE ttSnapShot THEN
        ASSIGN ttSnapShot.EndTime     = NOW
               ttSnapShot.ElapsedTime = ttSnapShot.EndTime - ttSnapShot.StartTime.

     /*--- Create a new snapshot ---*/ 
      ASSIGN CurrentSnapShotID = CurrentSnapShotId + 1.

      CREATE ttSnapShot.
      ASSIGN ttSnapShot.SnapShotID   = CurrentSnapShotID
             ttSnapShot.SnapShotName = ipSnapShotName
             ttSnapShot.StartTime    = NOW.
      
      /*--- Run through the databases and gather the stats ---*/
       FOR EACH ttDB:

         IF NOT CONNECTED(ttDB.DatabaseName) THEN NEXT.

         GetTableStats(ttDB.DatabaseName,
                       ttDB.ConnectID,
                       ttDB.StartingTS,
                       ttDB.EndingTS).

         GetIndexStats(ttDB.DatabaseName,
                       ttDB.ConnectID,
                       ttDB.StartingIS,
                       ttDB.EndingIS).

       END. /*--- EACH ttDB ---*/

      /*--- Turn on the profiler? ---*/
       IF UseProfiler = TRUE AND ttSnapShot.SnapShotName <> "*** End ***" THEN DO:
          PROFILER:ENABLED   = TRUE.
          PROFILER:DIRECTORY = SESSION:TEMP-DIRECTORY.
          PROFILER:FILE-NAME = SESSION:TEMP-DIRECTORY + "trace" + STRING(CurrentSnapShotID) + ".out".
          PROFILER:TRACE-FILTER = "".
          PROFILER:PROFILING = TRUE.
       END. /*--- Turn on the profiler ---*/

   END METHOD. /*--- StartSnapshot ---*/
  
  /*--- Method to write the data out to a file in HTML format ---*/
   METHOD PUBLIC STATIC VOID WriteHTML (INPUT ipFileName AS CHARACTER):

    /*--- Open the file ---*/
     OUTPUT STREAM stFile TO VALUE(ipFileName).

    /*--- Put out the html header/body etc ---*/
     PUT STREAM stFile UNFORMATTED
         '<!DOCTYPE HTML>' SKIP
         '<html>' SKIP
         '<head>' SKIP
		 '<title>Session Trace (TheMadDBA.com)</title>' SKIP
		 '<meta charset="utf-8" />' SKIP
		 '<meta name="viewport" content="width=device-width, initial-scale=1" />' SKIP
         '<style>' SKIP
         'body ~{ background-color:#FFFFFF; color: #060606;  font-family: Arial, Helvetica, sans-serif; font-size: 1em;~}' SKIP
         'h1 ~{ font-size: 2em; ~}' SKIP
         'h2 ~{ font-size: 1.5em; ~}' SKIP
         'h3 ~{ font-size: 1em; ~}' SKIP
         'table ~{ border: 1px solid #060606; border-collapse: collapse; margin:0.1em; ~}' SKIP
         'th ~{ border: 1px solid #060606; background-color: #d2f6ff; padding:0.5em; ~}' SKIP
         'td ~{ border: 1px solid #060606; padding: 0.2em; padding-left:0.5em; padding-right: 0.5em; ~}' SKIP
         '</style>' SKIP
	     '</head>' SKIP
	     '<body>' SKIP
         '<div style="padding: 1em;">' SKIP
         '<h1 id="TOP">Session Trace</h1>' SKIP.

     /*--- Put out the snapshots ---*/
      PUT STREAM stFile UNFORMATTED
         '<table><tr><th>Snapshot Name</th><th>Elapsed MS</th><th>Elapsed Seconds</th>'
         '<th>Percentage of Time</th><th>Percentage of Session Activity</th>'
         '<th>Total Activity</th><th>Table Activity</th><th>Index Activity</th></tr>' SKIP.

       FOR EACH ttSnapShot:
           PUT STREAM stFile UNFORMATTED
              '<tr><td>' ttSnapShot.SnapShotName '</td>'
              '<td>' STRING(ttSnapShot.ElapsedTime,">>>,>>>,>>>,>>>,>>9") '</td>'
              '<td>' STRING(ttSnapShot.ElapsedTime / 100,">>>,>>>,>>>,>>9.99") '</td>'
              '<td>' STRING(ttSnapShot.PercentTime,">>9.99") '</td>'
              '<td>' STRING(ttSnapShot.PercentActivity,">>9.99") '</td>'
              '<td>' STRING(ttSnapShot.TotalActivity,">>>,>>>,>>>,>>9") '</td>'
              '<td>' STRING(ttSnapShot.TableActivity,">>>,>>>,>>>,>>9") '</td>' 
              '<td>' STRING(ttSnapShot.IndexActivity,">>>,>>>,>>>,>>9") '</td>'
              '</tr>' SKIP.

       END. /*--- EACH ttSnapShot ---*/

       PUT STREAM stFile UNFORMATTED '</table>' SKIP.

      /*--- Put out the detailed sections ---*/
       FOR EACH ttSnapShot:

           /*--- Put out the header ---*/
            PUT STREAM stFile UNFORMATTED
               '<h2>Snapshot: ' ttSnapShot.SnapShotName '</h2>' SKIP
               '<div style="margin-left:3em;">'.

           /*--- Put out the table activity ---*/
            PUT STREAM stFile UNFORMATTED                            
               '<h3 id="Table_' STRING(ttSnapShot.SnapShotID) '">Table Activity</h3>' SKIP.

            PUT STREAM stFile UNFORMATTED
               '<table><tr><th>Database Name</th><th>Table Name</th>'
               '<th>Percent of Session Activity</th>'
               '<th>Percent of Snapshot Activity</th>'
               '<th>Total Activity</th>'
               '<th>Table Activity</th><th>Index Activity</th>'
               '<th>Reads</th><th>Creates</th><th>Updates</th><th>Deletes</th></tr>' SKIP.

           /*--- put out the tables ---*/
            FOR EACH ttTableStat OF ttSnapShot BY ttTableStat.TotalActivity DESCENDING:

                PUT STREAM stFile UNFORMATTED
                   '<tr><td>' ttTableStat.DatabaseName '</td>'
                   '<td>' ttTableStat.TableName '</td>'
                   '<td>' STRING(ttTableStat.PercentSession,">>9.99") '</td>'
                   '<td>' STRING(ttTableStat.PercentSnapShot,">>9.99") '</td>' 
                   '<td>' STRING(ttTableStat.TotalActivity,">>>,>>>,>>>,>>9") '</td>'
                   '<td>' STRING(ttTableStat.TableActivity,">>>,>>>,>>>,>>9") '</td>'
                   '<td>' STRING(ttTableStat.IndexActivity,">>>,>>>,>>>,>>9") '</td>'
                   '<td>' STRING(ttTableStat.Reads,">>>,>>>,>>>,>>9") '</td>'
                   '<td>' STRING(ttTableStat.Creates,">>>,>>>,>>>,>>9") '</td>'
                   '<td>' STRING(ttTableStat.Updates,">>>,>>>,>>>,>>9") '</td>'
                   '<td>' STRING(ttTableStat.Deletes,">>>,>>>,>>>,>>9") '</td>'
                   '</tr>' SKIP.

                    
            END. /*--- EACH ttTableStat ---*/

            PUT STREAM stFile UNFORMATTED
               '</table>' SKIP.

           /*--- Put out the index activity ---*/
            PUT STREAM stFile UNFORMATTED
              '<h3 id="Index_' STRING(ttSnapShot.SnapShotID) '">Index Activity</h3>' SKIP.

            PUT STREAM stFile UNFORMATTED
               '<table><tr><th>Database Name</th><th>Table Name</th><th>Index Name</th>'
               '<th>Percent of Session Activity</th>'
               '<th>Percent of Snapshot Activity</th>' 
               '<th>Index Activity</th>'
               '<th>Reads</th><th>Creates</th><th>Deletes</th>'
               '<th>Unique</th><th>Word Index</th>'
               '</tr>' SKIP.

           /*--- put out the indexes ---*/
            FOR EACH ttIndexStat OF ttSnapShot BY ttIndexStat.IndexActivity DESCENDING:

                FIND ttIndex WHERE
                     ttIndex.DatabaseName = ttIndexStat.DatabaseName AND
                     ttIndex.IndexNumber  = ttIndexStat.IndexNumber
                   NO-ERROR.

                PUT STREAM stFile UNFORMATTED
                   '<tr><td>' ttIndexStat.DatabaseName '</td>'
                   '<td>' ttIndexStat.TableName '</td>'
                   '<td>' ttIndexStat.IndexName '</td>'
                   '<td>' STRING(ttIndexStat.PercentSession,">>9.99") '</td>'
                   '<td>' STRING(ttIndexStat.PercentSnapshot,">>9.99") '</td>'
                   '<td>' STRING(ttIndexStat.IndexActivity,">>>,>>>,>>>,>>9") '</td>'
                   '<td>' STRING(ttIndexStat.Reads,">>>,>>>,>>>,>>9") '</td>'
                   '<td>' STRING(ttIndexStat.Creates,">>>,>>>,>>>,>>9") '</td>'
                   '<td>' STRING(ttIndexStat.Deletes,">>>,>>>,>>>,>>9") '</td>'
                   '<td>' STRING(ttIndex.IsUnique,"Y/N") '</td>'
                   '<td>' STRING(ttIndex.IsWord,"Y/N") '</td>'
                   '</tr>' SKIP.

                    
            END. /*--- EACH ttIndexStat ---*/

            PUT STREAM stFile UNFORMATTED
               '</table>' SKIP.

           /*--- Put out the profiler details? ---*/
            FIND FIRST ttSnapSource WHERE
                       ttSnapSource.SnapShotID = ttSnapShot.SnapShotID 
                     NO-ERROR.
            
            IF AVAILABLE ttSnapSource THEN DO:

               /*--- Put out the index activity ---*/
                PUT STREAM stFile UNFORMATTED
                  '<h3 id="Profile_' STRING(ttSnapShot.SnapShotID) '">Code Timing</h3>' SKIP.
    
                PUT STREAM stFile UNFORMATTED
                   '<table><tr><th>Code Section</th><th>Debug Line Number</th>'
                   '<th>Execution Count</th>'
                   '<th>Percentage of Session</th>'
                   '<th>Percentage of Snapshot</th>'
                   '<th>Time (ms)</th><th>Time (seconds)</th>'
                   '<th>Average time per execution (ms)</th>'
                   '</tr>' SKIP.


                FOR EACH ttSnapLineSummary WHERE
                         ttSnapLineSummary.SnapShotID = ttSnapShot.SnapShotID
                      BY ttSnapLineSummary.TotalTime DESCENDING:

                    FIND FIRST ttSnapSource WHERE
                               ttSnapSource.SnapShotID = ttSnapShot.SnapShotID AND
                               ttSnapSource.SourceID   = ttSnapLineSummary.SourceID
                             NO-ERROR.

                    PUT STREAM stFile UNFORMATTED
                        '<tr><td>' (IF AVAILABLE ttSnapSource THEN ttSnapSource.SourceName ELSE "Session") '</td>'
                        '<td>' STRING(ttSnapLineSummary.LineNumber,">>>,>>9") '</td>'
                        '<td>' STRING(ttSnapLineSummary.StatementCount,">>>,>>>,>>>,>>9") '</td>'
                        '<td>' STRING(ttSnapLineSummary.PercentSession,">>9.99") '</td>'
                        '<td>' STRING(ttSnapLineSummary.PercentSnapshot,">>9.99") '</td>'
                        '<td>' STRING(ttSnapLineSummary.TotalTime,">>>,>>>,>>>,>>9.9999999") '</td>'
                        '<td>' STRING(ttSnapLineSummary.TotalTime / 100 ,">>>,>>>,>>>,>>9.9999999") '</td>'
                        '<td>' STRING(ttSnapLineSummary.TotalTime / ttSnapLineSummary.StatementCount ,">>>,>>>,>>>,>>>,>>9.9999999") '</td>'
                        
                        '</tr>' SKIP.

                END. /*--- EACH ttSnapLineSummary ---*/

                PUT STREAM stFile UNFORMATTED '</table>' SKIP.


               
            END. /*--- AVAILABLE ttSnapSource ---*/

           /*--- End the div for this snapshot ----*/
            PUT STREAM stFile UNFORMATTED '</div>' SKIP.
       
       END. /*--- EACH ttSnapShot ---*/

    /*--- Close the HTML ---*/
     PUT STREAM stFile UNFORMATTED
        '</div>'  SKIP
        '</body>' SKIP
        '</html>' SKIP.

    /*--- Close the file ---*/ 
     OUTPUT STREAM stFile CLOSE.


   END METHOD. /*--- WriteHTML ---*/

  /*--- Method to write the data out to a file in text format ---*/
   METHOD PUBLIC STATIC VOID WriteText (INPUT ipFileName AS CHARACTER):

    /*--- Open the file ---*/
     OUTPUT STREAM stFile TO VALUE(ipFileName).

    /*--- Put out the html header/body etc ---*/
     FOR EACH ttSnapShot:
        EXPORT STREAM stFile ttSnapShot.
     END.

     PUT STREAM stFile UNFORMATTED "." SKIP.
    
    /*--- put out the tables ---*/
     FOR EACH ttTableStat:
        EXPORT STREAM stFile ttTableStat.
     END.
     
     PUT STREAM stFile UNFORMATTED "." SKIP.

    /*--- put out the indexes ---*/
     FOR EACH ttIndexStat:
        EXPORT STREAM stFile ttIndexStat.
     END.
     
     PUT STREAM stFile UNFORMATTED "." SKIP.
    
    /*--- put out the profiler source ---*/
     FOR EACH ttSnapSource:
        EXPORT STREAM stFile ttSnapSource.
     END.
     
     PUT STREAM stFile UNFORMATTED "." SKIP.
    
    /*--- put out the profiler lines ---*/
     FOR EACH ttSnapLineSummary:
        EXPORT STREAM stFile ttSnapLineSummary.
     END.
     
     PUT STREAM stFile UNFORMATTED "." SKIP.
    
    /*--- put out the table data lines ---*/
     FOR EACH ttTable:
        EXPORT STREAM stFile ttTable.
     END.
     
     PUT STREAM stFile UNFORMATTED "." SKIP.
    
    /*--- put out the index data lines ---*/
     FOR EACH ttIndex:
        EXPORT STREAM stFile ttIndex.
     END.
     
     PUT STREAM stFile UNFORMATTED "." SKIP.

    /*--- Close the file ---*/ 
     OUTPUT STREAM stFile CLOSE.

   END METHOD. /*--- WriteText ---*/

 END CLASS. /*--- SessionTrace ----*/
