-- Copyright (C) 1984  by Xerox Corporation. All rights reserved. 
-- CpuIdleImpl.mesa
-- Last edited by: Hoffman 27-Mar-84 11:03:48


DIRECTORY
  CourierInternal USING [numberOfStreams],
  CpuIdle,
  Inline USING [LongDiv, LowHalf, LongMult],
  Process USING [Detach, GetPriority, Milliseconds, MsecToTicks, Pause, Priority,
    SetPriority, SetTimeout, Yield],
  ProcessPriorities USING [priorityClientHigh, priorityClientLow],
  SpecialSpace USING [MakeProcedureResident, SpecialError],
  System USING [GetClockPulses, MicrosecondsToPulses, Pulses,
    PulsesToMicroseconds];
  
CpuIdleImpl: MONITOR
  IMPORTS CourierInternal, Inline, Process, SpecialSpace, System
  EXPORTS CpuIdle =
  BEGIN
  
  publicationMsecs: Process.Milliseconds = 1000;
  priorityPublisher: Process.Priority = ProcessPriorities.priorityClientHigh;
  priorityIdler: Process.Priority = ProcessPriorities.priorityClientLow;

  cpu, cpuTen: LONG CARDINAL ← 0;
  cyclesPerRefresh, pulsesPerRefresh: LONG CARDINAL ← 0;
  calibrated: BOOLEAN ← FALSE;
  cycleCount: LONG CARDINAL ← 0;
  publicationInterval: CONDITION;
  
  GetRawCpuUtilization: PUBLIC PROCEDURE RETURNS [LONG CARDINAL] =
    BEGIN
    RETURN[cpu];
    END;
    
  GetSmoothedCpuUtilization: PUBLIC PROCEDURE RETURNS [CARDINAL] =
    BEGIN
    RETURN[CARDINAL[cpuTen/100]];
    END;
    
  GetCalibration: PUBLIC PROCEDURE RETURNS[
    cyclesPerSecond, pulsesPerSecond: LONG CARDINAL, calibrateDone: BOOLEAN] =
    BEGIN
    RETURN[cyclesPerRefresh, pulsesPerRefresh, calibrated];
    END;
    
  GetCycles: PUBLIC PROCEDURE RETURNS[cycles: LONG CARDINAL] =
    BEGIN
    RETURN[cycleCount];
    END;

  StartIdler: PROCEDURE =
    BEGIN
    ENABLE UNWIND => NULL;
    priorityPrev: Process.Priority;

    Calibrate[];
    priorityPrev ← Process.GetPriority[];
    
    SpecialSpace.MakeProcedureResident[Idler!
      SpecialSpace.SpecialError => CONTINUE];
    Process.SetPriority[priorityIdler];
    Process.Detach[FORK Idler[]];
    
    Process.SetPriority[priorityPublisher];
    Process.SetTimeout[
      @publicationInterval, Process.MsecToTicks[publicationMsecs]];
    Process.Detach[FORK Updater[]];
    Process.SetPriority[priorityPrev];
    END;
  
  Calibrate: PROCEDURE =
    --  NOTE: If we get a frame fault with the priority set, calibration
    --        could be off by a considerable amount
    BEGIN
    cyclesToSample: CARDINAL = 10000;  -- about a second on a DLion
    priorityPrev: Process.Priority ← Process.GetPriority[];
    first, last: System.Pulses;
    nanoSecPerCycle: LONG CARDINAL;
    WHILE CourierConnections[] > 0 DO  -- because of the comment below
      Process.Pause[Process.MsecToTicks[1000]];
      ENDLOOP;
    Process.SetPriority[LAST[Process.Priority]];
    first ← System.GetClockPulses[];
    THROUGH [0..cyclesToSample) DO 
      cycleCount ← SUCC[cycleCount]; Process.Yield[]; ENDLOOP;
    -- This appears to go slow if an extra timeout scan happens, 
    --   packet arrives or ...
    -- Unfortunately, that causes confusion when idle > 100%
    last ← System.GetClockPulses[];
    cycleCount ← cycleCount - cyclesToSample;
    Process.SetPriority[priorityPrev];
    nanoSecPerCycle ←
      System.PulsesToMicroseconds[[LONG[1000]*(last - first)]]/cyclesToSample;
    cyclesPerRefresh ← (1000000*publicationMsecs)/nanoSecPerCycle;
    pulsesPerRefresh ← System.MicrosecondsToPulses[publicationMsecs*LONG[1000]];
    calibrated ← TRUE;
    END;
  

  CourierConnections: PROCEDURE RETURNS [CARDINAL] = INLINE
    BEGIN RETURN[CourierInternal.numberOfStreams] END;
    
  Idler: PROCEDURE =
    BEGIN
    DO -- forever
      THROUGH [0..10000) DO 
        cycleCount ← SUCC[cycleCount]; 
	Process.Yield[];
        ENDLOOP;
      ENDLOOP;
    END;
  
  Updater: ENTRY PROCEDURE =
    BEGIN
    startPulses: LONG CARDINAL ← System.GetClockPulses[];
    startCycles: LONG CARDINAL ← cycleCount;
    pulsesThisRefresh, cyclesThisRefresh: LONG CARDINAL ← 0;
    idle: CARDINAL ← 0;
    
    DO
      startPulses ← startPulses + pulsesThisRefresh;
      startCycles ← startCycles + cyclesThisRefresh;
      WAIT publicationInterval;
      -- get cpu utilization
      pulsesThisRefresh ← System.GetClockPulses[] - startPulses;
      cyclesThisRefresh ← cycleCount - startCycles;
      THROUGH [0..10) DO
        cyclesThisTry: LONG CARDINAL =
          cyclesThisRefresh*pulsesPerRefresh/pulsesThisRefresh;
        idle ← Inline.LowHalf[cyclesThisTry*10000/cyclesPerRefresh];
        IF idle > 10000 THEN {  -- Calibration screwed up
          cyclesPerRefresh ← cyclesPerRefresh + 1; 
	  LOOP}
        ELSE EXIT;
        REPEAT FINISHED => idle ← 10000;
        ENDLOOP;
      cpu ← (10000 - idle);
      cpuTen ← Inline.LongDiv[cpu + Inline.LongMult[CARDINAL[cpuTen],9], 10];
      ENDLOOP;

    END;
    

  -- Mainline Code
  Process.Detach[FORK StartIdler[]];
  
  END..