-- File MyPerfStatsImpl.mesa
-- Last edited by:
--   MBrown on 16-Feb-82  8:57:46
--  Kolling on November 14, 1983 11:39 am

  DIRECTORY
    BasicTime,
    IO,
    MyPerfStats,
    Rope;

MyPerfStatsImpl: CEDAR PROGRAM
  IMPORTS
    BasicTime,
    IO,
    Rope

  EXPORTS
    MyPerfStats
  SHARES
    MyPerfStats =
  BEGIN
  ROPE: TYPE = Rope.ROPE;
  STREAM: TYPE = IO.STREAM;
  
  -- Exported type

  Timer: TYPE = REF TimerObject;
  TimerObject: PUBLIC TYPE = RECORD[
    pName: ROPE,
    TimerWasStarted: BOOLEAN,
    TimeWhenStarted: LONG CARDINAL ← NULL,
    TotalElapsedTime: LONG CARDINAL ← NULL,
    MaxTime: LONG CARDINAL ← NULL,
    MinTime: LONG CARDINAL ← NULL,
    NStopTimerCalls: LONG CARDINAL ← NULL,
    next: Timer];

  Counter: TYPE = MyPerfStats.Counter;
  CounterObject: TYPE = MyPerfStats.CounterObject;

  -- Module state

  counterList: Counter ← NIL;
  timerList: Timer ← NIL;
  nGlitches: LONG CARDINAL ← 0;
    -- Counts number of improperly-matched Start - Stop calls.

  -- Procedures (also see inlines in MyPerfStats).

  CreateCounter: PUBLIC PROC[name: ROPE] RETURNS[Counter] = {
    TestForDuplicate: PROC [e: Counter] = {
      IF name.Equal[e.pName] THEN ERROR DuplicateName};
    EnumerateCounters[TestForDuplicate];
    {e: Counter ← NEW[CounterObject ←
      [pName: name, next: counterList]];
    InitializeCounter[e];
    RETURN[counterList ← e] }};

  DuplicateName: PUBLIC ERROR = CODE;

  InitializeCounter: PUBLIC PROC[event: Counter] = {
    event.counter ← 0 };

  DestroyCounter: PUBLIC PROC[event: Counter] = {
    Remove: PROC [e: Counter] = { IF e.next = event THEN e.next ← event.next };
    IF event = counterList THEN counterList ← event.next
    ELSE EnumerateCounters[Remove];
    event.pName ← NIL;  event.next ← NIL };

  EnumerateCounters: PROC[procToApply: PROC[Counter]] = {
    FOR p: Counter ← counterList, p.next UNTIL p=NIL DO
      procToApply[p]
    ENDLOOP };

  CreateTimer: PUBLIC PROC[name: ROPE] RETURNS[Timer] = {
    TestForDuplicate: PROC [e: Timer] = {
      IF name.Equal[e.pName] THEN ERROR DuplicateName};
    EnumerateTimers[TestForDuplicate];
    {e: Timer ← NEW[TimerObject ←
      [pName: name, TimerWasStarted: FALSE, next: timerList]];
    InitializeTimer[e];
    RETURN[timerList ← e] }};

  InitializeTimer: PUBLIC PROC[event: Timer] = {
    -- Don't reset a running timer.
    event.TotalElapsedTime ← 0;
    event.MaxTime ← 0;
    event.MinTime ← LAST[LONG CARDINAL];
    event.NStopTimerCalls ← 0 };

  DestroyTimer: PUBLIC PROC[event: Timer] = {
    Remove: PROC[e: Timer] = { IF e.next = event THEN e.next ← event.next };
    IF event = timerList THEN timerList ← event.next
    ELSE EnumerateTimers[Remove];
    event.pName ← NIL;  event.next ← NIL };

  EnumerateTimers: PROC[procToApply: PROC[Timer]] = {
    FOR p: Timer ← timerList, p.next UNTIL p=NIL DO
      procToApply[p]
    ENDLOOP };

  Initialize: PUBLIC PROC[] = {
    EnumerateCounters[InitializeCounter];
    EnumerateTimers[InitializeTimer] };

  Start: PUBLIC PROC[event: Timer] = TRUSTED{
    IF event.TimerWasStarted THEN nGlitches ← nGlitches + 1;
    event.TimerWasStarted ← TRUE;
    event.TimeWhenStarted ← BasicTime.GetClockPulses[] };

  Stop: PUBLIC PROC[event: Timer] = TRUSTED{
    IF ~event.TimerWasStarted THEN {
      nGlitches ← nGlitches + 1;
      RETURN };
    event.TimerWasStarted ← FALSE;
    event.NStopTimerCalls ← event.NStopTimerCalls + 1;
    -- What is done next is a function of how elaborate the stats need to be.  It might
    --even be a function of event.  For now, do something simple.
      {elapsedTime: LONG CARDINAL ← BasicTime.GetClockPulses[] - event.TimeWhenStarted;
      event.TotalElapsedTime ← event.TotalElapsedTime + elapsedTime;
      event.MaxTime ← MAX[elapsedTime, event.MaxTime];
      event.MinTime ← MIN[elapsedTime, event.MinTime] }
    };

  Print: PUBLIC PROC [heading: ROPE, oStream: STREAM, verbose: BOOLEAN] = {
    PrintHighResolutionTime: PROC[time: LONG CARDINAL] RETURNS[BOOLEAN] = TRUSTED{
      -- Returns TRUE iff time is less than 2 ms.
      time ← BasicTime.PulsesToMicroseconds[time]/100;
        -- convert to 1/10 ms units
        {ms: LONG CARDINAL ← time/10;
	tenthsecs: LONG CARDINAL ← time/1000;
	secs: LONG CARDINAL ← tenthsecs/10;
        decimal: LONG CARDINAL ← tenthsecs MOD 10;
        oStream.PutF["%g", IO.card[ms]];
        RETURN[ms <= 1]};
      };
    PrintCounter: PROC[e: Counter] = {
      IF e.counter > 0 THEN {
        oStream.PutF["%22g: %g events\n", IO.rope[e.pName], IO.card[e.counter]] }
      ELSE IF verbose THEN {
        oStream.PutF["%22g: no events\n", IO.rope[e.pName]] }
      };
    PrintTimer: PROC[e: Timer] = TRUSTED{
      IF e.NStopTimerCalls # 1 THEN ERROR;
      IF e.NStopTimerCalls > 0 THEN {
        timeIsSmall: BOOLEAN;
        timeIsSmall ← PrintHighResolutionTime[e.TotalElapsedTime/e.NStopTimerCalls];
        IF timeIsSmall THEN {
          avgTime: LONG CARDINAL ←
	    BasicTime.PulsesToMicroseconds[e.TotalElapsedTime/e.NStopTimerCalls]; 
          oStream.PutF[" ms. (%g us)", IO.card[avgTime]] }
        ELSE {
          oStream.PutRope[" ms."] }; }
      ELSE IF verbose THEN { oStream.PutF["%g: no events\n", IO.rope[e.pName]] };
      };
    IF heading # NIL AND heading.Size[] # 0 THEN oStream.PutF["%g", IO.rope[heading]];
    EnumerateCounters[PrintCounter];
    EnumerateTimers[PrintTimer];
    IF nGlitches > 0 THEN
      oStream.PutF["?%g out of order calls to Starting or Stopping!\n", IO.card[nGlitches]];
    nGlitches ← 0;
    oStream.PutF["\n"];
    oStream.Flush[];
  };

END.--PerfStatsImpl


CHANGE LOG

Created by MBrown on November 4, 1980  4:23 PM
-- By editing DBStatsImpl.

Changed by MBrown on November 6, 1980  3:30 PM
-- Uses DBLogStream instead of its own internal stream, to allow output
--to go to same file as DBStatsImpl.

Changed by MBrown on November 7, 1980  9:31 AM
-- Make InitializeCounterEvent and InitializeTimerEvent public.

Changed by MBrown on November 7, 1980  4:26 PM
-- Fix Print to make output take fewer lines.

Changed by MBrown on November 10, 1980  1:11 PM
-- Make ReadClock for Alto I faster (marginal improvement).  When average time is small, print it
--in microseconds.

Changed by MBrown on December 8, 1980  6:21 PM
-- Don't reset running timers in InitializeTimerEvent.

Changed by MBrown on January 10, 1981  9:29 PM
-- Created Pilot/collectible storage version.  Print now takes putChar and cleanup procs as parms.
--Renamed to PerfStatsImpl.

Changed by MBrown on January 11, 1981  5:02 PM
-- Added DuplicateName ERROR.  Added verbose parm to Print.

Changed by MBrown on 18-Aug-81 18:46:34
-- CedarString -> Rope (ugly since CWF does not know about Rope.)

Changed by MBrown on  7-Dec-81 16:02:11
-- Convert to use IO.

Changed by MBrown on 16-Feb-82  8:58:33
-- Remove LOOPHOLEs in dealing with times (compiler bug fixed.)