-- File PerfStatsImpl.mesa
-- Last edited by:
--   MBrown on February 7, 1984 3:13:36 pm PST

  DIRECTORY
    IO,
    BasicTime,
    PerfStats,
    Rope;

PerfStatsImpl: CEDAR PROGRAM
  IMPORTS
    IO,
    BasicTime,
    Rope
  EXPORTS
    PerfStats
  SHARES
    PerfStats =
  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 = PerfStats.Counter;
  CounterObject: TYPE = PerfStats.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 PerfStats).

  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] = {
    IF event.TimerWasStarted THEN nGlitches ← nGlitches + 1;
    event.TimerWasStarted ← TRUE;
    event.TimeWhenStarted ← BasicTime.GetClockPulses[] };

  Stop: PUBLIC PROC[event: Timer] = {
    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 [BOOL] = {
      -- 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;
        decimal: LONG CARDINAL ← time MOD 10;
        oStream.PutF["%g.%g", IO.card[ms], IO.card[decimal]];
        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] = {
      IF e.NStopTimerCalls > 0 THEN {
        timeIsSmall: BOOLEAN;
        oStream.PutF["%22g: ", IO.rope[e.pName]];
        timeIsSmall ← PrintHighResolutionTime[e.TotalElapsedTime/e.NStopTimerCalls];
        IF timeIsSmall THEN {
          avgTime: LONG CARDINAL ← BasicTime.PulsesToMicroseconds[
	         e.TotalElapsedTime/e.NStopTimerCalls]; 
          oStream.PutF[" ms (%g us) average time for %g events.",
            IO.card[avgTime], IO.card[e.NStopTimerCalls]]
          }
        ELSE {
          oStream.PutF[" ms average time for %g events.", IO.card[e.NStopTimerCalls]] };
          oStream.PutF["  Max = "];  [] ← PrintHighResolutionTime[e.MaxTime];
          oStream.PutF[", Min = "];  [] ← PrintHighResolutionTime[e.MinTime];
          oStream.PutF[".\n"]
          }
      ELSE IF verbose THEN {
        oStream.PutF["%g: no events\n", IO.rope[e.pName]] };
        };
    IF heading = NIL OR heading.Size[] = 0 THEN heading ← "PerfStats";
    oStream.PutF[IF verbose THEN "%g  (printing all events at %t)\n"
      ELSE "%g  (printing nonzero events at %t)\n", IO.rope[heading], IO.time[]];
    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\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 IOStream.

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

Changed by MBrown on June 24, 1982 1:21 pm
-- IOStream -> IO, CEDAR.  System procs GetClockPulses, PulsesToMicroseconds need to be SAFE.

Changed by MBrown on February 7, 1984 3:13:26 pm PST
-- Cedar 5.1.