<> <> <> <> <> <<>> 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: BOOLEAN _ FALSE; -- 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 { <> 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] = { <> millisSpentIdle _ millisSpentIdle + deltaMillis; WHILE millisSpentIdle >= 60*LONG[1000] DO minutesSpentIdle _ minutesSpentIdle + 1; millisSpentIdle _ millisSpentIdle - 60*LONG[1000]; ENDLOOP; }; IdleProcess: PROC = TRUSTED { <> 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[]]; }; <> WHILE NOT quit DO reads,writes,readPgs,writePgs: INT; diskPercent: REAL _ 0.0; deltaMillis: LONG CARDINAL; diskIO: INT _ 0; { <> nextMark: BasicTime.Pulses = BasicTime.GetClockPulses[]; idleTemp: INT _ watchStats.idleCount; <> delta: LONG CARDINAL _ BasicTime.PulsesToMicroseconds[nextMark - mark]; deltaMillis _ (delta + 500) / 1000; IF deltaMillis <= 10 THEN { <> Process.Pause[1]; LOOP}; mark _ nextMark; <> idleRate _ (idleTemp-lastIdle) / deltaMillis; lastIdle _ idleTemp; IF idleRate > maxIdleRate THEN <> IF deltaMillis > 100 THEN maxIdleRate _ idleRate ELSE idleRate _ maxIdleRate; }; { <> 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; }; }; { <> deltaWords: INT _ SafeStorage.NWordsAllocated[] - words; words _ words + deltaWords; wordsRate _ (deltaWords * 1000 + 500) / deltaMillis; }; { <> 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 { <> BasicTime.SetTime[]; millisSinceTimeSet _ 0; }; } ELSE minutesSpentIdle _ millisSpentIdle _ 0; { <> 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 { <> millisSinceLastBigBang _ 0; [] _ VM.Allocate[pagesInPartition+pagesInPartition ! VM.CantAllocate => {watchStats.vmRun _ bestInterval.count; CONTINUE}; ]; <> watchStats.gfiFree _ CountGFI[]; <> mark _ BasicTime.GetClockPulses[]; lastIdle _ watchStats.idleCount; }; }; <<>> <> watchStats.diskFree _ FileBackdoor.GetVolumePages[File.SystemVolume[]].free; <> ProcessorFace.SetMP[Real.RoundC[decayCpuRate * 100.0]]; <> WaitForUpdate[]; ENDLOOP; GO TO done; EXITS done => {quit _ TRUE}; }; <<**** Initialization code ****>> TRUSTED { Process.Detach[FORK Watcher]; }; END.