TTYWatch.mesa
Copyright © 1984, 1985 by Xerox Corporation. All rights reserved.
Doug Wyatt, April 25, 1985 0:29:36 am PST
Russ Atkinson (RRA) November 25, 1985 7:37:26 pm PST
Tim Diebert: June 26, 1986 2:38:32 pm PDT
DIRECTORY
BasicTime USING [GetClockPulses, Pulses, PulsesToMicroseconds, SetTime],
DebuggerSwap USING [WorryCallDebugger],
Disk USING [GetStatistics],
File USING [SystemVolume],
FileBackdoor USING [GetVolumePages],
FSBackdoor USING [RemoteEvent, RemoteOp],
PrincOps USING [GFT, GFTItem, PageCount, SD, sGFTLength],
PrincOpsUtils USING [ReadWDC],
Process USING [Detach, MsecToTicks, Pause, SecondsToTicks, SetPriority, SetTimeout],
ProcessorFace USING [SetMP],
Real USING [RoundC],
Rope USING [ROPE],
SafeStorage USING [NWordsAllocated],
VM USING [Allocate, CantAllocate, PageCount],
VMStatistics USING [VirtualAllocation],
WatchStats USING [WatchStatsRecord];
TTYWatch: CEDAR MONITOR
IMPORTS BasicTime, DebuggerSwap, Disk, File, FileBackdoor, PrincOpsUtils, Process, ProcessorFace, Real, SafeStorage, VM, VMStatistics
EXPORTS WatchStats
= BEGIN
**** Useful types from other interfaces ****
RemoteEventHandle: TYPE = REF READONLY FSBackdoor.RemoteEvent;
RemoteOp: TYPE = FSBackdoor.RemoteOp;
ROPE: TYPE = Rope.ROPE;
**** The following stats are exported via GetWatchStats ****
watchStats: WatchStats.WatchStatsRecord ← [0, 0, 0, 0, 0, 0, 0.0];
GetWatchStats: PUBLIC ENTRY PROC RETURNS [WatchStats.WatchStatsRecord] = {
RETURN [watchStats];
};
**** Global variables for Watch ****
quit: BOOLEANFALSE; -- watched by various processes for exit notification
waitCond: CONDITION;
fsPause: CONDITION ← [timeout: Process.MsecToTicks[200]];
defaultInterval: INT ← 16000;
defaultSample: INT ← 8;
longPause: INT ← 30 * 1000;
millisSinceLastBigBang: INT ← longPause;
minutesSpentIdle: INT ← 0;
millisSpentIdle: INT ← 0;
minutesSinceTimeSet: INT ← 0;
idleCpuRate: REAL ← 0.05; -- all samples must remain below this rate
decayCpuRate: REAL ← 0.0;
**** Procedures for Watch ****
CountGFI: PROC RETURNS [free: NAT] = TRUSTED {
This procedure counts the free GFIs via a linear scan of the table.
free ← 0;
FOR i: CARDINAL DECREASING IN [1..PrincOps.SD[PrincOps.sGFTLength]) DO
item: PrincOps.GFTItem = PrincOps.GFT[i];
IF item.data = 0 THEN free ← free + 1;
ENDLOOP;
};
WaitForUpdate: ENTRY PROC = {
ENABLE UNWIND => NULL;
WAIT waitCond;
};
SetPause: ENTRY PROC [parm: CARDINAL] = TRUSTED {
ENABLE UNWIND => NULL;
Process.SetTimeout[@waitCond, Process.SecondsToTicks[parm]];
BROADCAST waitCond;
};
TestForIdle: PROC [deltaMillis: INT] = {
We have been "idle" for during the last interval, so test for it being enough.
millisSpentIdle ← millisSpentIdle + deltaMillis;
WHILE millisSpentIdle >= 60*LONG[1000] DO
minutesSpentIdle ← minutesSpentIdle + 1;
millisSpentIdle ← millisSpentIdle - 60*LONG[1000];
ENDLOOP;
};
IdleProcess: PROC = TRUSTED {
There is only one idle process in the system at a time, and the idle process is the only one that runs at priority 0. The idle process checks for page faults while interrupts are disabled by looking at the wakeup disable counter.
Process.SetPriority[LOOPHOLE[0]]; -- priorityIdleProcess: Priority = 0
WHILE NOT quit DO
watchStats.idleCount ← watchStats.idleCount + 1;
IF PrincOpsUtils.ReadWDC[] # 0 THEN
DebuggerSwap.WorryCallDebugger["Idle with interrupts disabled?"];
ENDLOOP;
};
Watcher: PROC = {
ENABLE ABORTED => GO TO done;
words, wordsRate, oldWordsRate: INT ← 0;
oldActiveDiskPulses, oldTotalDiskPulses: BasicTime.Pulses ← 0;
oldDiskPercent: REAL ← 0.0;
maxIdleRate: INT ← 1;
oldIdleRate, idleRate, lastIdle: INT ← 0;
mark: BasicTime.Pulses ← 0;
millisSinceTimeSet: INT ← 0;
SetPause[2];
TRUSTED {
Process.Detach[FORK IdleProcess[]];
};
Now we come to the main Watcher loop
WHILE NOT quit DO
reads,writes,readPgs,writePgs: INT;
diskPercent: REAL ← 0.0;
deltaMillis: LONG CARDINAL;
diskIO: INT ← 0;
{
Get the time delta
nextMark: BasicTime.Pulses = BasicTime.GetClockPulses[];
idleTemp: INT ← watchStats.idleCount;
Make this follow the sampling of GetClockPulses as quickly as possible
delta: LONG CARDINAL ← BasicTime.PulsesToMicroseconds[nextMark - mark];
deltaMillis ← (delta + 500) / 1000;
IF deltaMillis <= 10 THEN {
Not enough precision, so wait for the minimum time, then loop
Process.Pause[1];
LOOP};
mark ← nextMark;
Update the idle rate data
idleRate ← (idleTemp-lastIdle) / deltaMillis;
lastIdle ← idleTemp;
IF idleRate > maxIdleRate THEN
If enough time has elapsed, then use new (higher) idle rate. If the test interval is too short, then don't trust the new idle rate (error is likely to be too high).
IF deltaMillis > 100 THEN maxIdleRate ← idleRate ELSE idleRate ← maxIdleRate;
};
{
Update the disk numbers
newActiveDiskPulses, newTotalDiskPulses: BasicTime.Pulses;
[active: newActiveDiskPulses, total: newTotalDiskPulses, reads: reads, writes: writes, readPages: readPgs, writePages: writePgs] ← Disk.GetStatistics[];
diskIO ← reads+writes;
IF newTotalDiskPulses # oldTotalDiskPulses THEN {
IF newActiveDiskPulses # oldActiveDiskPulses THEN {
diskPercent ← (1.0*(newActiveDiskPulses-oldActiveDiskPulses))
/ (newTotalDiskPulses-oldTotalDiskPulses);
oldActiveDiskPulses ← newActiveDiskPulses;
};
oldTotalDiskPulses ← newTotalDiskPulses;
};
};
{
Update the alloc data
deltaWords: INT ← SafeStorage.NWordsAllocated[] - words;
words ← words + deltaWords;
wordsRate ← (deltaWords * 1000 + 500) / deltaMillis;
};
{
Maintain the exponentially decaying CPU rate.
frac: REAL ← deltaMillis * 1E-4;
IF frac > 1.0 THEN frac ← 1.0;
watchStats.cpuLoad ← 1 - idleRate/(maxIdleRate*1.0);
decayCpuRate ← (1.0-frac)*decayCpuRate + frac*watchStats.cpuLoad;
};
millisSinceLastBigBang ← MIN[longPause, millisSinceLastBigBang + deltaMillis];
millisSinceTimeSet ← millisSinceTimeSet + deltaMillis;
IF decayCpuRate < idleCpuRate AND diskPercent = 0.0 AND wordsRate = 0
THEN {
TestForIdle[deltaMillis];
IF millisSinceTimeSet/60000 > minutesSinceTimeSet THEN {
Every 15 minutes we should reset the time to keep it from drifting too far
BasicTime.SetTime[];
millisSinceTimeSet ← 0;
};
}
ELSE minutesSpentIdle ← millisSpentIdle ← 0;
{
Sample the new VM & GFI numbers
pagesAllocated, pagesFreed, pagesInPartition: VM.PageCount;
TRUSTED {
temp: INT;
[pagesAllocated, pagesFreed, pagesInPartition]
← VMStatistics.VirtualAllocation[mds];
temp ← MAX[0, pagesInPartition - pagesAllocated + pagesFreed];
watchStats.mdsFree ← temp;
[pagesAllocated, pagesFreed, pagesInPartition]
← VMStatistics.VirtualAllocation[normalVM];
temp ← MAX[0, pagesInPartition - pagesAllocated + pagesFreed];
watchStats.vmFree ← temp;
};
IF millisSinceLastBigBang >= longPause THEN {
Calculate the stats for the max VM run (but only if they will be displayed). We do this infrequently since it it relatively expensive.
millisSinceLastBigBang ← 0;
[] ← VM.Allocate[pagesInPartition+pagesInPartition
! VM.CantAllocate => {watchStats.vmRun ← bestInterval.count; CONTINUE};
];
Calculate the # of GFIs free
watchStats.gfiFree ← CountGFI[];
To avoid a spike in the CPU usage (which can make it look like we are not idle when we really are), we sample the idle count and clock pulses again here.
mark ← BasicTime.GetClockPulses[];
lastIdle ← watchStats.idleCount;
};
};
Display Free line: disk, mds, gfi, VM, VM run
watchStats.diskFree ← FileBackdoor.GetVolumePages[File.SystemVolume[]].free;
Set the mp to the load.
ProcessorFace.SetMP[Real.RoundC[decayCpuRate * 100.0]];
Lastly, wait for the pause interval
WaitForUpdate[];
ENDLOOP;
GO TO done;
EXITS done => {quit ← TRUE};
};
**** Initialization code ****
TRUSTED {
Process.Detach[FORK Watcher];
};
END.