Watch.mesa
Copyright © 1984, 1985, 1986 by Xerox Corporation. All rights reserved.
Doug Wyatt, April 25, 1985 0:29:36 am PST
Russ Atkinson (RRA) May 10, 1986 1:12:58 pm PDT
DIRECTORY
BasicTime USING [GetClockPulses, Now, Pulses, PulsesToMicroseconds, PulsesToSeconds, SetTime, Unpack, Unpacked],
Booting USING [CallBootingProcs],
Buttons USING [Button, ButtonProc, Create, SetDisplayStyle],
CedarProcess USING [SetPriority],
Containers USING [ChildXBound, Create],
DebuggerSwap USING [WorryCallDebugger],
DefaultRemoteNames USING [Get],
Disk USING [GetStatistics],
EthernetDriverStats USING [EtherStats, EtherStatsRep, GetEthernetOneStats, GetEthernetStats],
File USING [SystemVolume],
FileBackdoor USING [GetVolumePages],
FileStats USING [Data, GetData],
FS USING [Error, ExpandName],
FSBackdoor USING [NextRemoteEvent, RemoteEvent, RemoteOp],
Imager USING [black, Color, MakeGray, MaskBox, SetColor, white],
Labels USING [Create, Label, Set, SetDisplayStyle],
Idle USING [IsIdle],
Icons USING [IconFlavor, NewIconFromFile],
IO USING [PutFR],
Menus USING [MenuProc],
NumberLabels USING [CreateNumber, NumberLabel, NumberLabelUpdate],
PrincOps USING [GFT, GFTItem, PageCount, SD, sGFTLength],
PrincOpsUtils USING [ReadWDC, WriteWDC],
Process USING [Detach, MsecToTicks, Pause, SecondsToTicks, SetTimeout],
ProcessorFace USING [PowerOff],
Real USING [Round, RoundI],
Rope USING [Concat, Find, Length, ROPE, Substr],
SafeStorage USING [NWordsAllocated, ReclaimCollectibleObjects, ReclamationReason, SetCollectionInterval, WaitForCollectorDone, WaitForCollectorStart],
UserProfile USING [Boolean, CallWhenProfileChanges, Number, ProfileChangedProc],
VFonts USING [StringWidth],
ViewerClasses USING [DestroyProc, PaintProc, Viewer, ViewerClass, ViewerClassRec],
ViewerOps USING [ComputeColumn, CreateViewer, PaintViewer, RegisterViewerClass, SetOpenHeight, ViewerColumn],
ViewerSpecs USING [openRightWidth],
VM USING [Allocate, CantAllocate, PageCount],
VMStatistics USING [checkoutConflicts, laundryCleanCalls, laundryWakeups, pageFaults, pagesCleaned, pagesCleanedPanic, panicLaundryWakeups, pinnedPages, readOnlyPages, rmAllocPasses, rmCleanPasses, rmDirty, rmFreeList, rmNewClean, rmOldClean, rmReclamations, swapInAlreadyIn, swapInCalls, swapInDirtyVictims, swapInFailedToCleanVictims, swapInNoRead, swapInPages, swapInPhysicalRuns, swapInReads, swapInVirtualRuns, uselessLaundryWakeups, VirtualAllocation],
WatchStats USING [WatchStatsRecord];
Watch: CEDAR MONITOR
IMPORTS BasicTime, Booting, Buttons, CedarProcess, Containers, DebuggerSwap, DefaultRemoteNames, Disk, EthernetDriverStats, File, FileBackdoor, FileStats, FS, FSBackdoor, Imager, Labels, Idle, Icons, IO, NumberLabels, PrincOpsUtils, Process, ProcessorFace, Real, Rope, SafeStorage, UserProfile, VFonts, ViewerOps, ViewerSpecs, VM, VMStatistics
EXPORTS WatchStats
= BEGIN
**** Useful types from other interfaces ****
Button: TYPE = Buttons.Button;
Label: TYPE = Labels.Label;
NumberLabel: TYPE = NumberLabels.NumberLabel;
RemoteEventHandle: TYPE = REF READONLY FSBackdoor.RemoteEvent;
RemoteOp: TYPE = FSBackdoor.RemoteOp;
ROPE: TYPE = Rope.ROPE;
**** Useful local types and constants ****
GraphData: TYPE = REF GraphDataRec;
GraphEntry: TYPE = RECORD [
value: REAL ← 0, -- current value (scaled)
updateHint: UpdateHint ← bigger,
fullScale: REAL]; -- log of full scale value (-1 = linear in [0..1])
UpdateHint: TYPE = {bigger, smaller, same};
GraphDataRec: TYPE = RECORD [top, middle, bottom: GraphEntry];
Parameter: TYPE = REF ParameterBlock;
ParameterBlock: TYPE = RECORD [
action: PROC [Parameter],
label: Label,
value,increment,lo,hi: INT];
IconArray: TYPE = REF IconArrayRep;
IconArrayRep: TYPE = ARRAY [0..7] OF Icons.IconFlavor;
**** The following stats are exported via GetWatchStats ****
watchStats: WatchStats.WatchStatsRecord;
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]];
newOpenHeight: INTEGER ← 0;
oldOpenHeight: INTEGER ← 0;
smallerOpenHeight: INTEGER ← 0;
middleOpenHeight: INTEGER ← 0;
biggerOpenHeight: INTEGER ← 0;
defaultInterval: INT ← 16000;
defaultSample: INT ← 2;
longPause: INT ← 30 * 1000;
millisSinceLastBigBang: INT ← longPause;
powerOffAfter: INT ← 1900;
powerOffBefore: INT ← 700;
idleMinutesTilPowerOff: INT ← 10;
minutesSpentIdle: INT ← 0;
millisSpentIdle: INT ← 0;
minutesSinceTimeSet: INT ← 0;
idleCpuRate: REAL ← 0.05; -- all samples must remain below this rate
decayCpuRate: REAL ← 0.0;
iconArray: IconArray ← NIL;
**** Procedures for Watch ****
myGrey: Imager.Color ~ Imager.MakeGray[0.5];
GraphPaint: ViewerClasses.PaintProc = {
data: GraphData = NARROW[self.data];
IF whatChanged = NIL THEN
data.bottom.updateHint ← data.middle.updateHint ← data.top.updateHint ← bigger;
SELECT data.top.updateHint FROM
bigger => {
Imager.SetColor[context, myGrey];
Imager.MaskBox[context, [0, 2*self.ch/3, data.top.value, self.ch]];
};
smaller => {
Imager.SetColor[context, Imager.white];
Imager.MaskBox[context, [data.top.value, 2*self.ch/3, self.cw, self.ch]];
};
ENDCASE;
SELECT data.middle.updateHint FROM
bigger => {
Imager.SetColor[context, myGrey];
Imager.MaskBox[context, [0, self.ch/3, data.middle.value, 2*self.ch/3]];
};
smaller => {
Imager.SetColor[context, Imager.white];
Imager.MaskBox[context, [data.middle.value, self.ch/3, self.cw, 2*self.ch/3]];
};
ENDCASE;
SELECT data.bottom.updateHint FROM
bigger => {
Imager.SetColor[context, myGrey];
Imager.MaskBox[context, [0, 0, data.bottom.value, self.ch/3]];
};
smaller => {
Imager.SetColor[context, Imager.white];
Imager.MaskBox[context, [data.bottom.value, 0, self.cw, self.ch/3]];
};
ENDCASE;
Imager.SetColor[context, Imager.black]
};
Log10: PROC [x: REAL] RETURNS [lx: REAL] = {
Log10[x] quickly returns
x < 1 => 0
x IN [1..1e6] => log(x) {absolute error < .0007}
x > 1e6 => 6
algorithm from Abramowitz: Handbook of Math Functions, p. 68
sqrt10: REAL = 3.162278;
t: REAL;
fast scale to [1..10], biased toward small sizes
SELECT x FROM
< 1e0 => {x ← 1.0; lx ← 0};
< 1e1 => lx ← 0;
< 1e2 => {x ← x * 1e-1; lx ← 1};
< 1e3 => {x ← x * 1e-2; lx ← 2};
< 1e4 => {x ← x * 1e-3; lx ← 3};
< 1e5 => {x ← x * 1e-4; lx ← 4};
< 1e6 => {x ← x * 1e-5; lx ← 5};
ENDCASE => {x ← 1.0; lx ← 6};
scale to [1..1/sqrt10]
IF x > sqrt10 THEN {x ← x*(1/sqrt10); lx ← lx + 0.5};
magic cubic approximation
t ← (x - 1)/(x + 1);
lx ← lx + (0.86304 + 0.36415*(t*t))*t;
};
GraphSet: PROC [graph: ViewerClasses.Viewer, t, m, b: REAL] = {
tempR: REAL;
myData: GraphData ← NARROW[graph.data];
IF myData = NIL THEN RETURN;
tempR ← IF myData.top.fullScale<0
THEN t*graph.cw
ELSE Log10[1 + t]*graph.cw/myData.top.fullScale;
myData.top.updateHint ← IF tempR > myData.top.value
THEN bigger
ELSE IF tempR < myData.top.value THEN smaller ELSE same;
myData.top.value ← tempR;
tempR ← IF myData.middle.fullScale<0
THEN m*graph.cw
ELSE Log10[1 + m]*graph.cw/myData.middle.fullScale;
myData.middle.updateHint ← IF tempR > myData.middle.value
THEN bigger
ELSE IF tempR < myData.middle.value THEN smaller ELSE same;
myData.middle.value ← tempR;
tempR ← IF myData.bottom.fullScale<0
THEN b*graph.cw
ELSE Log10[1 + b]*graph.cw/myData.bottom.fullScale;
myData.bottom.updateHint ← IF tempR > myData.bottom.value
THEN bigger
ELSE IF tempR < myData.bottom.value THEN smaller ELSE same;
myData.bottom.value ← tempR;
IF NOT graph.parent.iconic THEN
ViewerOps.PaintViewer[graph, client, FALSE, $Update];
};
AdjustParameter: Menus.MenuProc = {
[parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL]
IF NOT quit THEN {
viewer: ViewerClasses.Viewer ← NARROW[parent];
data: Parameter ← NARROW[clientData];
new: INT ← data.value;
IF mouseButton = red
THEN {
IF data.increment = 0
THEN new ← new * 2
ELSE new ← new + data.increment}
ELSE {
IF data.increment = 0
THEN new ← new / 2
ELSE new ← new - data.increment};
IF new < data.lo THEN new ← data.lo;
IF new > data.hi THEN new ← data.hi;
IF data.value # new THEN {
data.value ← new;
data.action[data];
SetLabel[data.label, data.value]};
};
};
SetLabel: PROC [label: NumberLabel, value: INT] = {
IF NOT quit AND NOT label.destroyed THEN
NumberLabels.NumberLabelUpdate[label, value];
};
FindPosAfter: PROC [base: ROPE, object: ROPE, pos: INT ← 0] RETURNS [INT] = {
start: INT ← Rope.Find[s1: base, pos1: pos, s2: object, case: FALSE];
IF start < 0 THEN RETURN [pos];
RETURN [start + object.Length[]];
};
WaitForFSUpdate: ENTRY PROC = {WAIT fsPause};
DisplayFSStatus: PROC [readFSlabel, writeFSlabel, flushFSlabel, NreadFSlabel, NwriteFSlabel, NflushFSlabel: Label] = {
readCount: INT ← 0;
writeCount: INT ← 0;
flushCount: INT ← 0;
h: RemoteEventHandle ← NIL;
r: ROPE;
UNTIL quit DO
countLabel, nameLabel: Label ← NIL;
count: INT ← 0;
style: ATOM ← $BlackOnWhite;
WaitForFSUpdate[];
h ← FSBackdoor.NextRemoteEvent[h];
r ← h.fName;
IF quit THEN EXIT;
SELECT h.op FROM
startRetrieving => {
nameLabel ← readFSlabel; countLabel ← NreadFSlabel;
count ← readCount ← readCount + 1;
style ← $WhiteOnBlack;
};
endRetrieving => {
nameLabel ← readFSlabel; countLabel ← NreadFSlabel;
count ← readCount;
};
startStoring => {
nameLabel ← writeFSlabel; countLabel ← NwriteFSlabel;
count ← writeCount ← writeCount + 1;
style ← $WhiteOnBlack;
};
endStoring => {
nameLabel ← writeFSlabel; countLabel ← NwriteFSlabel;
count ← writeCount;
};
startFlushing => {
nameLabel ← flushFSlabel; countLabel ← NflushFSlabel;
count ← flushCount ← flushCount + 1;
style ← $WhiteOnBlack;
};
endFlushing => {
nameLabel ← flushFSlabel; countLabel ← NflushFSlabel;
count ← flushCount;
};
ENDCASE => LOOP;
Labels.Set[nameLabel, r.Substr[FindPosAfter[r, DefaultRemoteNames.Get[].systemHost]]];
Labels.SetDisplayStyle[nameLabel, style];
SetLabel[countLabel, count];
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.
CedarProcess.SetPriority[idle];
WHILE NOT quit DO
watchStats.idleCount ← watchStats.idleCount + 1;
IF PrincOpsUtils.ReadWDC[] # 0 THEN TRUSTED {
DebuggerSwap.WorryCallDebugger["Idle with interrupts disabled?"];
Interrupts have been found to be disabled. This is often due to some joker disabling interrupts and getting an address fault. If the person exaining this situation finds it to be benign, then proceeding this call to the debugger will enable interrupts and allow some salvage work to go on.
PrincOpsUtils.WriteWDC[0];
Enable interrupts.
};
ENDLOOP;
};
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;
};
CauseGCHit: Menus.MenuProc = TRUSTED {
[parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL]
IF NOT quit THEN {
viewer: ViewerClasses.Viewer ← NARROW[parent];
Buttons.SetDisplayStyle[viewer, $BlackOnGrey];
SafeStorage.ReclaimCollectibleObjects[TRUE, mouseButton = blue];
IF control OR shift THEN ForceSampleEntry[];
Buttons.SetDisplayStyle[viewer, $BlackOnWhite];
};
};
CollectorWatcher: PROC [gcStatusLabel: Label] = {
ENABLE ABORTED => GO TO done;
active: ROPE = "BUSY: ";
inactive: ROPE = "done.";
WHILE NOT quit DO
cReasons: ARRAY SafeStorage.ReclamationReason OF ROPE =
["client", "clientTandS", "clientNoTrace", "rcTableOverflow",
"allocationInterval", "quantaNeeded", "finalizationThreshold"];
wrds,objs: LONG CARDINAL;
GCnumber: INT;
msg: ROPE ← inactive;
r: SafeStorage.ReclamationReason← SafeStorage.WaitForCollectorStart[].reason;
IF quit THEN EXIT;
TRUSTED{ Labels.Set[gcStatusLabel, active.Concat[cReasons[r]] ] };
[incarnation: GCnumber, reason: r, wordsReclaimed: wrds, objectsReclaimed: objs] ←
SafeStorage.WaitForCollectorDone[];
IF quit THEN EXIT;
Labels.Set[
gcStatusLabel,
IO.PutFR[" (GC#%g got %g words, %g objs)",
[integer[GCnumber]], [cardinal[wrds]], [cardinal[objs]] ]
];
ENDLOOP;
EXITS done => {};
};
SetGCInt: PROC [parm: Parameter] = {
[] ← SafeStorage.SetCollectionInterval[parm.value];
};
MyDestroy: ENTRY ViewerClasses.DestroyProc = {
ENABLE UNWIND => NULL;
viewer: ViewerClasses.Viewer ← NARROW[self];
quit ← TRUE;
BROADCAST waitCond;
};
WaitForUpdate: ENTRY PROC = {
ENABLE UNWIND => NULL;
WAIT waitCond;
};
SetPause: ENTRY PROC [parm: Parameter] = TRUSTED {
ENABLE UNWIND => NULL;
Process.SetTimeout[@waitCond, Process.SecondsToTicks[parm.value]];
BROADCAST waitCond;
};
ForceSample: Menus.MenuProc = {
[parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL]
ENABLE UNWIND => NULL;
viewer: ViewerClasses.Viewer ← NARROW[parent];
SELECT mouseButton FROM
red => newOpenHeight ← smallerOpenHeight;
yellow => newOpenHeight ← middleOpenHeight;
blue => newOpenHeight ← biggerOpenHeight;
ENDCASE;
ForceSampleEntry[];
};
ForceSampleEntry: ENTRY PROC = {
[parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL]
ENABLE UNWIND => NULL;
millisSinceLastBigBang ← longPause;
BROADCAST waitCond;
};
ProfileChanged: UserProfile.ProfileChangedProc = {
[reason: UserProfile.ProfileChangeReason]
defaultInterval ← UserProfile.Number["Watch.GCInterval", defaultInterval];
defaultSample ← UserProfile.Number["Watch.SamplePause", 2];
longPause ← UserProfile.Number["Watch.LongPause", 30] * 1000;
idleMinutesTilPowerOff ← UserProfile.Number["Watch.idleMinutesTilPowerOff", 10];
IF idleMinutesTilPowerOff < 5 THEN idleMinutesTilPowerOff ← 5;
IF UserProfile.Boolean["Watch.powerOffInhibit", FALSE] THEN
idleMinutesTilPowerOff ← 1000000;
powerOffAfter ← UserProfile.Number["Watch.powerOffAfter", 1900];
SELECT powerOffAfter FROM
< 0 => powerOffAfter ← 0;
> 2400 => powerOffAfter ← 2400;
ENDCASE;
powerOffBefore ← UserProfile.Number["Watch.powerOffBefore", 700];
SELECT powerOffBefore FROM
< 0 => powerOffBefore ← 0;
> 2400 => powerOffBefore ← 2400;
ENDCASE;
idleCpuRate ← UserProfile.Number["Watch.idleCpuRate", 5]*1E-2;
SELECT idleCpuRate FROM
< 0.0 => idleCpuRate ← 0.0;
> 100.0 => idleCpuRate ← 100.0;
ENDCASE;
minutesSinceTimeSet ← UserProfile.Number["Watch.minutesSinceTimeSet", 15];
IF minutesSinceTimeSet <= 0 THEN minutesSinceTimeSet ← LAST[INT];
};
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;
IF Idle.IsIdle[] AND minutesSpentIdle >= idleMinutesTilPowerOff THEN {
unpack: BasicTime.Unpacked ← BasicTime.Unpack[BasicTime.Now[]];
minuteTime: INT ← unpack.hour*100+unpack.minute;
IF minuteTime > powerOffAfter OR minuteTime < powerOffBefore THEN {
It is the right time, and we have been idle for long enough, so let's power off the machine to save power.
IF Booting.CallBootingProcs[] = NIL THEN ProcessorFace.PowerOff[];
};
};
};
BlinkBase: PROC [viewer: ViewerClasses.Viewer] = {
DO
IF viewer.destroyed THEN EXIT;
viewer.icon ← iconArray[0];
IF viewer.iconic THEN ViewerOps.PaintViewer[viewer, all];
Process.Pause[Process.SecondsToTicks[10]];
IF NOT viewer.iconic THEN LOOP;
FOR i: NAT IN [0..7] DO
viewer.icon ← iconArray[i];
IF viewer.iconic THEN ViewerOps.PaintViewer[viewer, all] ELSE EXIT;
Process.Pause[Process.MsecToTicks[100]];
ENDLOOP;
FOR i: NAT DECREASING IN [0..7] DO
viewer.icon ← iconArray[i];
IF viewer.iconic THEN ViewerOps.PaintViewer[viewer, all] ELSE EXIT;
Process.Pause[Process.MsecToTicks[100]];
ENDLOOP;
ENDLOOP;
};
Watcher: PROC = {
ENABLE ABORTED => GO TO done;
container, graph: ViewerClasses.Viewer ← NIL;
wordsLabel, decayCpuLabel, diskIOLabel: Label ← NIL;
Free line labels
diskLabel, gfiLabel, mdsLabel, freeVMLabel, maxFreeLabel: Label ← NIL;
Idle labels
minutesIdleLabel: Label ← NIL;
millisIdleLabel: Label ← NIL;
FS watcher labels
readFSlabel, writeFSlabel, flushFSlabel: Label ← NIL;
NreadFSlabel, NwriteFSlabel, NflushFSlabel: Label ← NIL;
Disk read/write labels
diskPercentQueuedLabel, diskActiveSecsLabel, diskTotalSecsLabel: Label ← NIL;
diskReadLabel, diskWriteLabel, diskReadPgsLabel, diskWritePgsLabel: Label ← NIL;
VM labels
vmFaultsLabel, vmReadOnlyLabel: Label ← NIL;
vmPinnedPagesLabel, vmCheckoutConflictsLabel: Label ← NIL;
Replacement labels
rmAllocPassesLabel, rmReclamationsLabel: Label ← NIL;
rmFreeListLabel, rmOldCleanLabel, rmNewCleanLabel, rmDirtyLabel: Label ← NIL;
Laundry labels
rmCleanPassesLabel, laundryWakeupsLabel, pagesCleanedLabel: Label ← NIL;
panicLaundryWakeupsLabel, pagesCleanedPanicLabel: Label ← NIL;
uselessLaundryWakeupsLabel, laundryCleanCallsLabel: Label ← NIL;
SwapIn labels
swapInCallsLabel, swapInVirtualRunsLabel, swapInPhysicalRunsLabel: Label ← NIL;
swapInPagesLabel, swapInAlreadyInLabel: Label ← NIL;
swapInNoReadLabel, swapInReadsLabel: Label ← NIL;
swapInDirtyVictimsLabel, swapInFailedToCleanVictimsLabel: Label ← NIL;
FileStats labels (stored as triplets)
Triplet: TYPE = REF TripletRep;
TripletRep: TYPE = RECORD [calls, pages, msecs: Label];
fsOpenTriplet: Triplet ← NIL;
fsCreateTriplet: Triplet ← NIL;
fsDeleteTriplet: Triplet ← NIL;
fsExtendTriplet: Triplet ← NIL;
fsContractTriplet: Triplet ← NIL;
fsReadTriplet: Triplet ← NIL;
fsWriteTriplet: Triplet ← NIL;
words, wordsRate, oldWordsRate: INT ← 0;
oldActiveDiskPulses, oldTotalDiskPulses: BasicTime.Pulses ← 0;
oldDiskPercent: REAL ← 0.0;
etherStatsData: REF EtherStatsData ← NIL;
EtherStatsData: TYPE = RECORD [
ref: EthernetDriverStats.EtherStats ← NIL,
old: EthernetDriverStats.EtherStatsRep,
time: BasicTime.Pulses,
packetsIn, wordsInRate, wordsIn: Labels.Label ← NIL,
packetsOut, wordsOutRate, wordsOut: Labels.Label ← NIL];
maxIdleRate: INT ← 1;
oldIdleRate, idleRate, lastIdle: INT ← 0;
mark: BasicTime.Pulses;
millisSinceTimeSet: INT ← 0;
pauseParm: Parameter ←
NEW[ParameterBlock ← [SetPause, NIL, defaultSample, 0, 1, 64]];
gcParm: Parameter ←
NEW[ParameterBlock ← [SetGCInt, NIL, defaultInterval, 0, 1000, 512000]];
SetFileStats: PROC [data: FileStats.Data, trip: Triplet] = {
SetLabel[trip.calls, data.calls];
SetLabel[trip.pages, data.pages];
SetLabel[trip.msecs, (BasicTime.PulsesToMicroseconds[data.pulses]+500)/1000];
};
UpdateEtherStats: PROC = {
IF etherStatsData # NIL THEN {
We have some ether stats to display
sample: EthernetDriverStats.EtherStatsRep ← etherStatsData.ref^;
time: BasicTime.Pulses ← BasicTime.GetClockPulses[];
delta: REAL ← BasicTime.PulsesToSeconds[time - etherStatsData.time];
SetLabel[etherStatsData.packetsIn, sample.packetsRecv];
SetLabel[etherStatsData.wordsInRate, Real.Round[(sample.wordsRecv - etherStatsData.old.wordsRecv)/delta]];
SetLabel[etherStatsData.wordsIn, sample.wordsRecv];
SetLabel[etherStatsData.packetsOut, sample.packetsSent];
SetLabel[etherStatsData.wordsOutRate, Real.Round[(sample.wordsSent - etherStatsData.old.wordsSent)/delta]];
SetLabel[etherStatsData.wordsOut, sample.wordsSent];
etherStatsData.old ← sample;
etherStatsData.time ← time;
};
};
BuildTool: PROC = {
BuildTool is a separate procedure to factor out the work done to create a viewer from the work done to refresh the Watch tool. This is especially important to reduce the liklihood of overflowing storage in Pass 5 of the compiler, I think.
graphClass: ViewerClasses.ViewerClass =
NEW[ViewerClasses.ViewerClassRec ← [paint: GraphPaint, destroy: MyDestroy]];
gapX: INTEGER ← 2;
gapY: INTEGER ← 0;
lastX: INTEGER ← 0;
nextX: INTEGER ← gapX;
nextY: INTEGER ← 0;
AddLabel: PROC [prefix: ROPE, chars: NAT ← 0, lastInLine: BOOLFALSE, ww: INTEGER ← 0] RETURNS [label: Label] = {
IF chars # 0
THEN {
label ← Labels.Create[
info: [name: prefix, parent: container, border: FALSE, wx: nextX, wy: nextY, ww: ww],
paint: FALSE];
nextX ← nextX + label.ww;
label ← NumberLabels.CreateNumber[
info: [name: NIL, parent: container, border: FALSE, wx: nextX, wy: nextY],
chars: chars,
paint: FALSE];
}
ELSE
label ← Labels.Create [
info: [name: prefix, parent: container, border: FALSE, wx: nextX, wy: nextY, ww: ww],
paint: FALSE];
lastX ← nextX ← label.wx + label.ww + gapX;
IF lastInLine THEN {
nextX ← gapX; nextY ← label.wy + label.wh + gapY};
};
AddTriplet: PROC [lead: ROPE, ww: INTEGER] RETURNS [trip: Triplet] = {
IF lead # NIL THEN [] ← AddLabel[lead, 0, FALSE, ww];
trip ← NEW[TripletRep ← [AddLabel["calls", 6], NIL, NIL]];
Must do this here, since order of evaluation matters
trip.pages ← AddLabel["pages", 7];
trip.msecs ← AddLabel["msecs", 8, TRUE];
};
AddButton: PROC [buttonName: ROPENIL, chars: NAT ← 0, parm: Parameter ← NIL, proc: Buttons.ButtonProc ← NIL, lastInLine: BOOLFALSE] RETURNS [button: Buttons.Button] = {
label: ViewerClasses.Viewer ← NIL;
IF proc = NIL THEN proc ← AdjustParameter;
button ← Buttons.Create [
info: [name: buttonName, parent: container, wx: nextX, wy: nextY, border: TRUE],
fork: TRUE, proc: proc, clientData: parm, paint: FALSE];
nextX ← nextX + button.ww;
SELECT TRUE FROM
chars # 0 =>
label ← NumberLabels.CreateNumber[
info: [name: NIL, parent: container, border: FALSE, wx: nextX, wy: nextY],
chars: chars,
paint: FALSE];
parm # NIL => label ← Labels.Create [
info: [name: NIL, parent: container, border: FALSE, wx: nextX, wy: nextY],
paint: FALSE];
ENDCASE;
IF label # NIL THEN {
nextX ← nextX + label.ww;
IF parm # NIL THEN parm.label ← label;
};
lastX ← nextX ← nextX + gapX;
IF lastInLine THEN {
nextX ← gapX;
nextY ← button.wy + button.wh + gapY};
};
SimpleLabel: PROC [name: ROPE, wx,wy: INTEGER] = {
[] ← Labels.Create[info: [name: name, parent: container, wx: wx, wy: wy, border: FALSE], paint: FALSE];
};
AddEtherStats: PROC = {
ref: EthernetDriverStats.EtherStats ← EthernetDriverStats.GetEthernetOneStats[0];
IF ref = NIL THEN ref ← EthernetDriverStats.GetEthernetStats[0];
IF ref = NIL THEN RETURN;
etherStatsData ← NEW[EtherStatsData];
etherStatsData.ref ← ref;
etherStatsData.old ← ref^;
etherStatsData.time ← BasicTime.GetClockPulses[];
etherStatsData.packetsIn ← AddLabel["Ether packets in", 10];
etherStatsData.wordsInRate ← AddLabel["wds/sec", 6];
etherStatsData.wordsIn ← AddLabel["words", 11, TRUE];
etherStatsData.packetsOut ← AddLabel["Ether packets out", 9];
etherStatsData.wordsOutRate ← AddLabel["wds/sec", 6];
etherStatsData.wordsOut ← AddLabel["words", 11, TRUE];
};
CreateGraph: PROC RETURNS[viewer: ViewerClasses.Viewer] = {
W2: PROC [r: ROPE] RETURNS [INTEGER] = {RETURN[VFonts.StringWidth[r]/2]};
xTemp: INTEGER;
yTemp: INTEGER;
wordsLabel ← AddLabel["Words", 9, TRUE]; xTemp ← lastX;
decayCpuLabel ← AddLabel["CPU Load", 3, TRUE]; xTemp ← MAX[xTemp, lastX];
diskIOLabel ← AddLabel["DiskIO", 9, TRUE]; xTemp ← MAX[xTemp, lastX];
viewer ← ViewerOps.CreateViewer[
flavor: $BarGraph,
info: [parent: container, wx: xTemp,
wy: wordsLabel.wy + wordsLabel.wh, ww: ViewerSpecs.openRightWidth - xTemp - 5,
wh: diskIOLabel.wy - (wordsLabel.wy + wordsLabel.wh),
data: NEW[GraphDataRec ← [[fullScale: 5], [fullScale: -1], [fullScale: -1]]]],
paint: FALSE];
xTemp ← viewer.ww/5;
yTemp ← wordsLabel.wy;
SimpleLabel["1", viewer.wx - W2["1"], yTemp];
SimpleLabel["10", viewer.wx + xTemp - W2["10"], yTemp];
SimpleLabel["100", viewer.wx + 2*xTemp - W2["100"], yTemp];
SimpleLabel["1000", viewer.wx + 3*xTemp - W2["1000"], yTemp];
SimpleLabel["10000", viewer.wx + 4*xTemp - W2["10000"], yTemp];
xTemp ← viewer.ww/4;
yTemp ← diskIOLabel.wy;
SimpleLabel["0%", viewer.wx - W2["1"], yTemp];
SimpleLabel["25%", viewer.wx + xTemp - W2["3"], yTemp];
SimpleLabel["50%", viewer.wx + xTemp*2 - W2["10"], yTemp];
SimpleLabel["75%", viewer.wx + xTemp*3 - W2["30"], yTemp];
};
build enclosing viewer
container ← Containers.Create[info: [name: "Watch", iconic: TRUE, icon: iconArray[0], column: right, scrollable: FALSE]];
line 0: bar graphs
ViewerOps.RegisterViewerClass[$BarGraph, graphClass];
graph ← CreateGraph[];
line 1: mds, gfi, disk, freeVM
[] ← AddLabel["Free "];
diskLabel ← AddLabel["disk", 6];
gfiLabel ← AddLabel["gfi", 3];
mdsLabel ← AddLabel["mds", 3];
freeVMLabel ← AddLabel["VM", 5];
maxFreeLabel ← AddLabel["VM run", 5, TRUE];
line 2: gc interval, button & status
gapY ← 1;
[] ← AddButton[buttonName: "GC interval", chars: 6, parm: gcParm];
SetLabel[gcParm.label, gcParm.value];
SetGCInt[gcParm];
[] ← AddButton[buttonName: "GC", proc: CauseGCHit];
{
gcStatusLabel: Label = AddLabel["inactive.", 0, TRUE, ViewerSpecs.openRightWidth];
Containers.ChildXBound[container, gcStatusLabel];
TRUSTED {Process.Detach[FORK CollectorWatcher[gcStatusLabel]]};
};
line 3: sample button and FS status
[] ← AddButton[buttonName: "Sample", proc: ForceSample];
[] ← AddButton[buttonName: "interval", chars: 2, parm: pauseParm];
SetLabel[pauseParm.label, pauseParm.value];
SetPause[pauseParm];
readFSlabel ← AddLabel[
"FS status (inverted iff busy)", 0, TRUE, ViewerSpecs.openRightWidth];
Containers.ChildXBound[container, readFSlabel];
middleOpenHeight ← readFSlabel.wy + readFSlabel.wh * MAX[LONG[1], MIN[LONG[30], UserProfile.Number["Watch.middleLines", 2]]+1];
set an aesthetically sized open window
newOpenHeight ← oldOpenHeight ← smallerOpenHeight ← nextY;
ViewerOps.SetOpenHeight[container, oldOpenHeight];
Add labels below this line for the BIG size of Watch.
line 4: file being flushed
gapY ← 0;
[] ← AddLabel["Flushing "];
flushFSlabel ← AddLabel[
"(FS file being flushed)", 0, TRUE, ViewerSpecs.openRightWidth];
Containers.ChildXBound[container, flushFSlabel];
line 5: file being stored
[] ← AddLabel["Storing "];
writeFSlabel ← AddLabel[
"(FS file being stored)", 0, TRUE, ViewerSpecs.openRightWidth];
Containers.ChildXBound[container, writeFSlabel];
line 6: FS counts
[] ← AddLabel["FS "];
NreadFSlabel ← AddLabel["fetches", 5];
NflushFSlabel ← AddLabel["flushes", 5];
NwriteFSlabel ← AddLabel["stores", 5, TRUE];
line 6A1: Ether stats
AddEtherStats[];
line 6B: Idle minutes
minutesIdleLabel ← AddLabel["Idle minutes", 5];
millisIdleLabel ← AddLabel["millis", 5, TRUE];
line 7: Disk stats
[] ← AddLabel["Disk "];
diskPercentQueuedLabel ← AddLabel["% busy", 3];
diskActiveSecsLabel ← AddLabel["secs busy", 6];
diskTotalSecsLabel ← AddLabel["secs total", 7, TRUE];
diskReadLabel ← AddLabel[" reads", 6];
diskReadPgsLabel ← AddLabel["rPgs", 7];
diskWriteLabel ← AddLabel["writes", 6];
diskWritePgsLabel ← AddLabel["wPgs", 7, TRUE];
line 8: VM stats
[] ← AddLabel["VM "];
vmFaultsLabel ← AddLabel["faults", 7];
vmReadOnlyLabel ← AddLabel["readOnly", 5];
vmPinnedPagesLabel ← AddLabel["pinned", 5];
vmCheckoutConflictsLabel ← AddLabel["conflicts", 4, TRUE];
line 9: replacement stats
[] ← AddLabel["Replacement "];
rmAllocPassesLabel ← AddLabel["passes", 4];
rmReclamationsLabel ← AddLabel["pages", 6, TRUE];
rmFreeListLabel ← AddLabel[" free", 6];
rmOldCleanLabel ← AddLabel["old", 6];
rmNewCleanLabel ← AddLabel["new", 6];
rmDirtyLabel ← AddLabel["dirty", 6, TRUE];
line 10: laundry stats
[] ← AddLabel["Laundry "];
rmCleanPassesLabel ← AddLabel["passes", 3];
pagesCleanedLabel ← AddLabel["pages", 7];
laundryWakeupsLabel ← AddLabel["wakeups", 8, TRUE];
panicLaundryWakeupsLabel ← AddLabel[" panic", 3];
pagesCleanedPanicLabel ← AddLabel["panicPgs", 4];
laundryCleanCallsLabel ← AddLabel["cleanCalls", 5];
uselessLaundryWakeupsLabel ← AddLabel["useless", 8, TRUE];
line 11: SwapIn stats
[] ← AddLabel["SwapIn "];
swapInCallsLabel ← AddLabel["calls", 6];
swapInVirtualRunsLabel ← AddLabel["vRuns", 6];
swapInPhysicalRunsLabel ← AddLabel["pRuns", 6, TRUE];
swapInPagesLabel ← AddLabel[" pages", 7];
swapInAlreadyInLabel ← AddLabel["alreadyIn", 7];
swapInNoReadLabel ← AddLabel["undef", 7];
swapInReadsLabel ← AddLabel["read", 7, TRUE];
swapInDirtyVictimsLabel ← AddLabel[" dirtyVictims", 5];
swapInFailedToCleanVictimsLabel ← AddLabel["cleanFailed", 5, TRUE];
{-- line 11: FileStats stuff
ww: INTEGER;
[] ← AddLabel["File Statistics", 0, TRUE];
ww ← AddLabel[" Open "].ww;
fsOpenTriplet ← AddTriplet[NIL, ww];
fsCreateTriplet ← AddTriplet[" Create", ww];
fsDeleteTriplet ← AddTriplet[" Delete", ww];
fsExtendTriplet ← AddTriplet[" Extend", ww];
fsContractTriplet ← AddTriplet[" Contract", ww];
fsReadTriplet ← AddTriplet[" Read", ww];
fsWriteTriplet ← AddTriplet[" Write", ww];
};
biggerOpenHeight ← nextY;
initialize measurments
mark ← BasicTime.GetClockPulses[];
watchStats.idleCount ← 0;
words ← SafeStorage.NWordsAllocated[];
TRUSTED {
Process.Detach[FORK IdleProcess[]];
Process.Detach[FORK DisplayFSStatus[readFSlabel, writeFSlabel, flushFSlabel, NreadFSlabel, NwriteFSlabel, NflushFSlabel]];
};
};
BuildTool[];
TRUSTED {
Process.Detach[FORK BlinkBase[container]];
};
Now we come to the main Watcher loop
WHILE NOT quit AND NOT container.destroyed 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;
Test for a valid viewer still existing
IF quit OR container.destroyed THEN GO TO done;
IF container.iconic OR Idle.IsIdle[] THEN {
The user really does not want to see the data now. We performed the above calculations just to keep the data current (and to avoid overflow in some numbers).
Process.Pause[Process.SecondsToTicks[2]];
LOOP;
};
{
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 AND NOT container.iconic 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;
};
};
IF newOpenHeight # oldOpenHeight THEN {
The desired open height has changed, so actually make the change here.
ViewerOps.SetOpenHeight[container, oldOpenHeight ← newOpenHeight];
IF NOT container.iconic THEN
ViewerOps.ComputeColumn[ViewerOps.ViewerColumn[container]];
};
Display Free line: disk, mds, gfi, VM, VM run
SetLabel[diskLabel, watchStats.diskFree ← FileBackdoor.GetVolumePages[File.SystemVolume[]].free];
SetLabel[mdsLabel, watchStats.mdsFree];
SetLabel[gfiLabel, watchStats.gfiFree];
SetLabel[freeVMLabel, watchStats.vmFree];
SetLabel[maxFreeLabel, watchStats.vmRun];
{
Display gc interval, button & status & CPU avg. rate
SetLabel[wordsLabel, words];
SetLabel[diskIOLabel, diskIO];
IF oldDiskPercent # diskPercent OR oldWordsRate # wordsRate OR oldIdleRate # idleRate THEN {
oldIdleRate ← idleRate;
GraphSet[
graph,
oldWordsRate ← wordsRate,
watchStats.cpuLoad,
oldDiskPercent ← diskPercent];
};
SetLabel[decayCpuLabel, Real.RoundI[decayCpuRate*100.0]];
};
IF container.wh > smallerOpenHeight THEN {
There is more than the minimum amount of information on the screen, so we try to update those numbers as well.
Idle info
UpdateEtherStats[];
Idle info
SetLabel[minutesIdleLabel, minutesSpentIdle];
SetLabel[millisIdleLabel, millisSpentIdle];
Disk stats
SetLabel[diskPercentQueuedLabel, Real.RoundI[oldDiskPercent*100.0]];
SetLabel[
diskActiveSecsLabel,
(BasicTime.PulsesToMicroseconds[oldActiveDiskPulses]+500000)/1000000];
SetLabel[
diskTotalSecsLabel,
(BasicTime.PulsesToMicroseconds[oldTotalDiskPulses]+500000)/1000000];
SetLabel[diskReadLabel, reads];
SetLabel[diskWriteLabel, writes];
SetLabel[diskReadPgsLabel, readPgs];
SetLabel[diskWritePgsLabel, writePgs];
VM stats
SetLabel[vmFaultsLabel, VMStatistics.pageFaults];
SetLabel[vmReadOnlyLabel, VMStatistics.readOnlyPages];
SetLabel[vmPinnedPagesLabel, VMStatistics.pinnedPages];
SetLabel[vmCheckoutConflictsLabel, VMStatistics.checkoutConflicts];
Replacement stats
SetLabel[rmAllocPassesLabel, VMStatistics.rmAllocPasses];
SetLabel[rmReclamationsLabel, VMStatistics.rmReclamations];
SetLabel[rmFreeListLabel, VMStatistics.rmFreeList];
SetLabel[rmOldCleanLabel, VMStatistics.rmOldClean];
SetLabel[rmNewCleanLabel, VMStatistics.rmNewClean];
SetLabel[rmDirtyLabel, VMStatistics.rmDirty];
Laundry stats
SetLabel[rmCleanPassesLabel, VMStatistics.rmCleanPasses];
SetLabel[laundryWakeupsLabel, VMStatistics.laundryWakeups];
SetLabel[pagesCleanedLabel, VMStatistics.pagesCleaned];
SetLabel[panicLaundryWakeupsLabel, VMStatistics.panicLaundryWakeups];
SetLabel[pagesCleanedPanicLabel, VMStatistics.pagesCleanedPanic];
SetLabel[uselessLaundryWakeupsLabel, VMStatistics.uselessLaundryWakeups];
SetLabel[laundryCleanCallsLabel, VMStatistics.laundryCleanCalls];
SwapIn stats
SetLabel[swapInCallsLabel, VMStatistics.swapInCalls];
SetLabel[swapInVirtualRunsLabel, VMStatistics.swapInVirtualRuns];
SetLabel[swapInPhysicalRunsLabel, VMStatistics.swapInPhysicalRuns];
SetLabel[swapInPagesLabel, VMStatistics.swapInPages];
SetLabel[swapInAlreadyInLabel, VMStatistics.swapInAlreadyIn];
SetLabel[swapInNoReadLabel, VMStatistics.swapInNoRead];
SetLabel[swapInReadsLabel, VMStatistics.swapInReads];
SetLabel[swapInDirtyVictimsLabel, VMStatistics.swapInDirtyVictims];
SetLabel[swapInFailedToCleanVictimsLabel, VMStatistics.swapInFailedToCleanVictims];
FileStats stuff
SetFileStats[FileStats.GetData[open], fsOpenTriplet];
SetFileStats[FileStats.GetData[create], fsCreateTriplet];
SetFileStats[FileStats.GetData[delete], fsDeleteTriplet];
SetFileStats[FileStats.GetData[extend], fsExtendTriplet];
SetFileStats[FileStats.GetData[contract], fsContractTriplet];
SetFileStats[FileStats.GetData[read], fsReadTriplet];
SetFileStats[FileStats.GetData[write], fsWriteTriplet];
};
Lastly, wait for the pause interval
WaitForUpdate[];
ENDLOOP;
GO TO done;
EXITS done => {quit ← TRUE};
};
**** Initialization code
TRUSTED {
iconFileName: ROPEFS.ExpandName["Watch.icons"].fullFName;
iconArray ← NEW[IconArrayRep ← ALL[tool]];
FOR i: NAT IN [0..7] DO
iconArray[i] ← Icons.NewIconFromFile[iconFileName, i ! FS.Error => EXIT];
ENDLOOP;
UserProfile.CallWhenProfileChanges[ProfileChanged];
Process.Detach[FORK Watcher];
};
END.