Watch.mesa
Copyright © 1984, 1985 by Xerox Corporation. All rights reserved.
Andrew Birrell, November 12, 1982 10:07 am
Paul Rovner, March 1, 1983 5:21 pm
Russ Atkinson, January 28, 1985 12:43:05 pm PST
DIRECTORY
BasicTime USING [GetClockPulses, Now, Pulses, PulsesToMicroseconds, Unpack, Unpacked],
Buttons USING [Button, ButtonProc, Create, SetDisplayStyle],
Containers USING [ChildXBound, Create],
DebuggerSwap USING [WorryCallDebugger],
DefaultRemoteNames USING [Get],
Disk USING [GetStatistics],
File USING [GetVolumePages, SystemVolume],
FileStats USING [Data, GetData],
FSExtras USING [NextRemoteEvent, RemoteEvent, RemoteOp],
Graphics USING [black, DrawBox, SetColor, SetStipple, white],
Labels USING [Create, Label, Set, SetDisplayStyle],
IdleExtras USING [IdleHandler, RegisterIdleHandler],
IO USING [PutFR],
Menus USING [MenuProc],
NumberLabels USING [CreateNumber, NumberLabel, NumberLabelUpdate],
PrincOps USING [GFT, GFTItem, PageCount, SD, sGFTLength],
Process USING [Detach, MsecToTicks, Pause, SecondsToTicks, SetPriority, SetTimeout],
ProcessorFace USING [PowerOff],
Real USING [RoundI],
Rope USING [Concat, Find, Length, ROPE, Substr],
SafeStorage USING [NWordsAllocated, ReclaimCollectibleObjects, ReclamationReason, SetCollectionInterval, WaitForCollectorDone, WaitForCollectorStart],
UserProfile USING [CallWhenProfileChanges, Number, ProfileChangedProc],
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, Buttons, Containers, DebuggerSwap, DefaultRemoteNames, Disk, File, FileStats, FSExtras, Graphics, Labels, IdleExtras, IO, NumberLabels, Process, ProcessorFace, Real, Rope, SafeStorage, UserProfile, 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 FSExtras.RemoteEvent;
RemoteOp: TYPE = FSExtras.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];
**** 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;
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;
idleStatus: {asleep, awake} ← awake;
idleCpuRate: REAL ← 0.04; -- all samples must remain below this rate
decayCpuRate: REAL ← 0.0;
**** Procedures for Watch ****
GraphPaint: ViewerClasses.PaintProc = {
myGrey: CARDINAL = 122645B;
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 => {
Graphics.SetStipple[context, myGrey];
Graphics.DrawBox[context, [0, 2*self.ch/3, data.top.value, self.ch]]};
smaller => {
Graphics.SetColor[context, Graphics.white];
Graphics.DrawBox[context, [data.top.value, 2*self.ch/3, self.cw, self.ch]]};
ENDCASE;
SELECT data.middle.updateHint FROM
bigger => {
Graphics.SetStipple[context, myGrey];
Graphics.DrawBox[context, [0, self.ch/3, data.middle.value, 2*self.ch/3]]};
smaller => {
Graphics.SetColor[context, Graphics.white];
Graphics.DrawBox[context, [data.middle.value, self.ch/3, self.cw, 2*self.ch/3]]};
ENDCASE;
SELECT data.bottom.updateHint FROM
bigger => {
Graphics.SetStipple[context, myGrey];
Graphics.DrawBox[context, [0, 0, data.bottom.value, self.ch/3]]};
smaller => {
Graphics.SetColor[context, Graphics.white];
Graphics.DrawBox[context, [data.bottom.value, 0, self.cw, self.ch/3]]};
ENDCASE;
Graphics.SetColor[context, Graphics.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 TRUSTED{
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 ← FSExtras.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.
Process.SetPriority[0];
WHILE NOT quit DO
watchStats.idleCount ← watchStats.idleCount + 1;
IF PrincOpsUtils.ReadWDC[] # 0 THEN
DebuggerSwap.WorryCallDebugger["Idle with interrupts disabled?"];
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: CARDINAL;
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)",
[cardinal[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];
IF control THEN newOpenHeight ← smallerOpenHeight;
IF shift THEN newOpenHeight ← biggerOpenHeight;
ForceSampleEntry[];
};
ForceSampleEntry: ENTRY PROC = {
[parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL]
ENABLE UNWIND => NULL;
millisSinceLastBigBang ← longPause;
BROADCAST waitCond;
};
NoticeIdleTransition: IdleExtras.IdleHandler = {
[data: REF ANY, reason: IdleExtras.IdleReason]
SELECT reason FROM
becomingIdle => idleStatus ← asleep;
becomingBusy => idleStatus ← awake;
ENDCASE => ERROR;
minutesSpentIdle ← millisSpentIdle ← 0;
};
ProfileChanged: UserProfile.ProfileChangedProc = {
[reason: UserProfile.ProfileChangeReason]
defaultInterval ← UserProfile.Number["Watch.GCInterval", 16000];
defaultSample ← UserProfile.Number["Watch.SamplePause", 2];
longPause ← UserProfile.Number["Watch.LongPause", 30] * 1000;
idleMinutesTilPowerOff ← UserProfile.Number["Watch.idleMinutesTilPowerOff", 10];
IF idleMinutesTilPowerOff < 10 THEN idleMinutesTilPowerOff ← 10;
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", 4]*1E-2;
SELECT idleCpuRate FROM
< 0.0 => idleCpuRate ← 0.0;
> 100.0 => idleCpuRate ← 100.0;
ENDCASE;
};
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 idleStatus = asleep 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.
ProcessorFace.PowerOff[];
};
};
};
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;
gapX: INTEGER = 2;
gapY: INTEGER = 1;
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;
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];
};
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]];
lastX: INTEGER;
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];
};
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];
}; --CreateGraph--
UserProfile.CallWhenProfileChanges[ProfileChanged];
[] ← IdleExtras.RegisterIdleHandler[NoticeIdleTransition];
build enclosing viewer
container ← Containers.Create[
info: [name: "Watch", iconic: TRUE, 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
[] ← 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];
set an aesthetically sized open window
newOpenHeight ← oldOpenHeight ← smallerOpenHeight ← nextY + gapY - 1;
ViewerOps.SetOpenHeight[container, oldOpenHeight];
Add labels below this line for the BIG size of Watch.
line 4: file being flushed
[] ← 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 6A: 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 + gapY - 1;
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[];
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;
};
IF decayCpuRate < idleCpuRate AND diskPercent = 0.0 AND wordsRate = 0
THEN TestForIdle[deltaMillis]
ELSE minutesSpentIdle ← millisSpentIdle ← 0;
millisSinceLastBigBang ← MIN[longPause, millisSinceLastBigBang + deltaMillis];
Test for a valid viewer still existing
IF quit OR container.destroyed THEN GO TO done;
IF container.iconic OR idleStatus = asleep 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 ← File.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
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 {
Process.Detach[FORK Watcher];
};
END.