/* tblmon.p
 *
 * program to interactively monitor table statistics
 *
 * known bugs:
 *
 *   time rollover at midnight is not handled properly
 *
 *
 */

define variable interval as integer no-undo initial 2.
define variable show_top as integer no-undo initial 20.

define variable sort-criteria as character no-undo initial "i" case-sensitive.

define variable tlist as character no-undo format "x(70)".

define variable r as integer no-undo.
define variable t as integer no-undo.

define variable s_time as integer no-undo.

define variable cum-total as integer no-undo.
define variable int-total as integer no-undo.

define temp-table tt_xstat no-undo
  field xid       as integer
  field xname     as character format "x(20)" label "Table"
  field base-stat    as integer
  field last-stat    as integer
  field this-stat    as integer
  field cum-stat     as integer format ">,>>>,>>9" label "Accum"
  field int-stat     as integer format ">,>>>,>>9" label "Inter"
  field cum-rate     as integer format ">,>>>,>>9" label "Accum"
  field int-rate     as integer format ">,>>>,>>9" label "Inter"
  field cum-pct      as integer format ">>9.99%"   label "Accum"
  field int-pct      as integer format ">>9.99%"   label "Inter"
  index int-idx int-stat
  index cum-idx cum-stat
  index xidx is unique primary xid.

define query q for tt_xstat.

procedure show-help:

  display
    "Table Read Monitor" "Page 1 of 2" to 75 skip
    skip(1)
    "Monitors table READ statistics available from the _TableStat VST." skip
    "You must have VSTs enabled and may need to adjust the -tablebase" skip
    "and -tablerangesize startup parameters if you have more than 50" skip
    "tables or wish to monitor a subset of your tables." skip
    skip(1)
    "The monitor interatively samples read activity against every table" skip
    "and displays the total number of records read since starting, the" skip
    "number read in the interval, the rate of reads per second since" skip
    "starting, the rate for the most recent interval and the percentages" skip
    "of total reads (across all tables) for both samples.  Data is sorted" skip
    "by the most active table for either the interval or cumulatively." skip
    "" skip
    "The overall read rate, both cumulatively and for the interval, is also" skip
    "shown." skip
    "" skip
   with
    frame show-help1
    row 1.

  display
    "Table Read Monitor" "Page 2 of 2" to 75 skip
    skip(1)
    "Commands include:" skip
    skip(1)
    "  ^d, q, Q -- Quit the program." skip
    "  ^h, h, H -- Obtain this help message." skip
    "      i, I -- Adjust the monitoring interval." skip
    "      r, R -- Change the number of rows displayed." skip
    "      s, S -- Sort criteria:" skip
    "               i interval descending." skip
    "               I interval ascending." skip
    "               c cumulative descending." skip
    "               C cumulative ascending." skip
    "      t, T -- Comma delimited list of tables to track." skip
    "      z, Z -- Re-initialize the counters." skip
    "" skip
   with
    frame show-help2
    row 1.

  pause.

  hide frame show-help1 no-pause.
  hide frame show-help2 no-pause.

  return.

end.

procedure initialize:

  /* initial values for baseline
   *
   */

  s_time = time.

  for each _TableStat no-lock:

    find first _File no-lock where _File._File-Num = _TableStat._TableStat-Id no-error.

    if ( available _File ) then
      do:

        find first tt_xstat exclusive-lock where tt_xstat.xid = _tablestat._tablestat-id no-error.
        if not available tt_xstat then create tt_xstat.
        assign      
          tt_xstat.xid       = _tablestat._tablestat-id
          tt_xstat.xname     = _file._file-name
          tt_xstat.base-stat = _tablestat._tablestat-read
          tt_xstat.last-stat = _tablestat._tablestat-read
          tt_xstat.this-stat = _tablestat._tablestat-read
        .

      end.

  end.

  return.

end.

procedure upd-stats:

  /* update the sample
   *
   */

  for each _TableStat no-lock:

    find tt_xstat exclusive-lock where tt_xstat.xid = _tablestat._tablestat-id no-error.

    if not available tt_xstat then
      next.  /* this should be impossible... at least until online schema changes happen */

    tt_xstat.this-stat = _TableStat._TableStat-Read.

  end.

  assign
    cum-total = 0
    int-total = 0
    t = time - s_time
  .

  for each tt_xstat exclusive-lock:

    assign
      tt_xstat.cum-stat  = tt_xstat.this-stat -  tt_xstat.base-stat
      tt_xstat.cum-rate  = tt_xstat.cum-stat / t
      tt_xstat.int-stat  = tt_xstat.this-stat - tt_xstat.last-stat
      tt_xstat.int-rate  = tt_xstat.int-stat / interval
      tt_xstat.last-stat = tt_xstat.this-stat
      cum-total = cum-total + tt_xstat.cum-stat
      int-total = int-total + tt_xstat.int-stat
    .

  end.

  for each tt_xstat exclusive-lock:
    assign
      tt_xstat.cum-pct = ( tt_xstat.cum-stat / ( if cum-total = 0 then 1 else cum-total )) * 100
      tt_xstat.int-pct = ( tt_xstat.int-stat / ( if int-total = 0 then 1 else int-total )) * 100
    .
  end.

  return.

end.

/************************************************************
 ************************************************************

 The Main Event

 ************************************************************
 ************************************************************/

run initialize.

/* the monitoring loop
 *
 */

do while true:

  display
    string( time, "hh:mm:ss") "Table Read Monitor" to 50 today to 80 skip
    skip(1)
    "Overall Cumulative Rate:" integer( cum-total / t ) format ">,>>>,>>9/sec" "     Overall Interval Rate:" integer( int-total / interval ) format ">,>>>,>>9/sec" skip
    skip(1)
                "Total" to 40    "Rate"  to 62    "Percentage" to 80 skip
   with
    no-box
    no-labels.

  run upd-stats.

  case sort-criteria:

    when "I" then 
      open query q for each tt_xstat no-lock by tt_xstat.int-stat.

    when "i" then 
      open query q for each tt_xstat no-lock by tt_xstat.int-stat descending.

    when "C" then 
      open query q for each tt_xstat no-lock by tt_xstat.cum-stat.

    when "c" then 
      open query q for each tt_xstat no-lock by tt_xstat.cum-stat descending.

  end.

  clear frame a all no-pause.

  r = 0.
  do while r = 0 or available tt_xstat:

    get next q.

    if not available tt_xstat then leave.

    if tlist <> "" then if lookup( tt_xstat.xname, tlist ) = 0 then next.

    r = r + 1.
    if r > show_top then leave.
    
    display
      tt_xstat.xname
      tt_xstat.cum-stat to 30
      tt_xstat.int-stat to 40
      tt_xstat.cum-rate to 52
      tt_xstat.int-rate to 62
      tt_xstat.cum-pct  to 72
      tt_xstat.int-pct  to 80
     with
      frame a
      row 6
      show_top down
      no-box.

    down 1 with frame a.

  end.

  close query q.

  /* wait interval for next sample or process a user command if something is typed
   *
   */

  pause interval no-message.

  if (( lastkey =   4 ) or		/* ^d	*/
      ( lastkey =  81 ) or		/*  Q	*/
      ( lastkey = 113 )) then leave.	/*  q	*/

   else if (( lastkey =   8 ) or	/* ^h	*/
      ( lastkey =  63 ) or		/*  ?	*/
      ( lastkey =  72 ) or		/*  H	*/
      ( lastkey = 104 )) then		/*  h	*/
    do:
      run show-help.
    end.

   else if (
      ( lastkey =  73 ) or		/*  I	*/
      ( lastkey = 105 )) then		/*  i	*/
    do:
      message "New interval:" update interval.
    end.

   else if (
      ( lastkey =  82 ) or		/*  R	*/
      ( lastkey = 114 )) then		/*  r	*/
    do:
      message "Show top rows:" update show_top.
    end.

   else if (
      ( lastkey =  83 ) or		/*  S	*/
      ( lastkey = 115 )) then		/*  s	*/
    do:
      message "Sort criteria:" update sort-criteria.
    end.

   else if (
      ( lastkey =  84 ) or		/*  T	*/
      ( lastkey = 116 )) then		/*  t	*/
    do:
      message "Track:" update tlist.
    end.

  else if (
      ( lastkey =  90 ) or		/*  Z	*/
      ( lastkey = 122 )) then		/*  z	*/
    do:
      run initialize.
    end.

end.

return.

