Watch.mesa; written by S. McGregor
McGregor, August 8, 1983 12:31 pm
Andrew Birrell, November 12, 1982 10:07 am
Russ Atkinson, May 4, 1983 9:26 pm
Paul Rovner, March 1, 1983 5:21 pm
Last Edited by: Pausch, August 12, 1983 5:51 pm
DIRECTORY
Buttons USING [Button, ButtonProc, Create, SetDisplayStyle],
CIFSFeedback USING [Register],
Containers USING [ChildXBound, Create],
Convert USING [ValueToRope],
DiskDriverSharedImpl USING [totalNumberOfRequests],
File USING [Capability, nullCapability, ShowCapability],
Imager USING [black, MaskIntRectangle, SetColor, white],
Labels USING [Create, Label, Set, SetDisplayStyle],
NumberLabels USING [CreateNumber, NumberLabel, NumberLabelUpdate],
PrincOpsRuntime USING [GFT, GFTItem],
Process USING [Detach, Pause, SecondsToTicks, SetPriority, SetTimeout],
Real USING [RoundI],
Rope USING [Cat, Concat, Find, Length, Replace, ROPE],
RTProcess USING [GetTotalPageFaults, StartWatchingFaults, StopWatchingFaults],
RTQuanta USING [PagesPerQuantum],
RTRefCounts USING
[AMapOiOe, AMapPiRce, GetMapOiOe, GetMapPiRce, mapOiOeLimit, mapOiOeStart, OverflowEntry, ProbeSize, rceEmpty, RCEntry, rcFinalize, RefCt],
RTZones USING [MapQZf, mzVacant, TMapQZf, ZoneFinger],
Runtime USING [IsBound],
SafeStorage USING
[NWordsAllocated, ReclaimCollectibleObjects, ReclamationReason, TrimAllZones, TrimRootBase, SetCollectionInterval, WaitForCollectorDone, WaitForCollectorStart],
SDDefs USING [SD, sGFTLength],
ShowTime USING [GetMark, Microseconds],
Space USING
[GetAttributes, GetHandle, GetWindow, Handle, mds, nullHandle, PageFromLongPointer, PageNumber, virtualMemory],
System USING [GetGreenwichMeanTime, gmtEpoch, GreenwichMeanTime],
UserProfile USING [Number],
ViewerClasses USING [DestroyProc, PaintProc, Viewer, ViewerClass, ViewerClassRec],
ViewerOps USING
[ComputeColumn, CreateViewer, PaintViewer, RegisterViewerClass, SetOpenHeight, ViewerColumn],
ViewerSpecs USING [openRightWidth],
Volume USING [GetAttributes, SystemID];
Watch: CEDAR MONITOR
IMPORTS
Buttons, CIFSFeedback, Containers, Convert, DiskDriverSharedImpl, File, Imager, Labels, NumberLabels, Process, Real, Rope, RTProcess, RTRefCounts, RTZones, Runtime, SafeStorage, ShowTime, Space, System, UserProfile, ViewerOps, ViewerSpecs, Volume
SHARES
DiskDriverSharedImpl
= BEGIN
**** Useful types from other interfaces ****
Button: TYPE = Buttons.Button;
Label: TYPE = Labels.Label;
NumberLabel: TYPE = NumberLabels.NumberLabel;
ROPE: TYPE = Rope.ROPE;
**** Useful local types and constants ****
NormalSize: CARDINAL = RTRefCounts.ProbeSize;
OverflowSize: CARDINAL = RTRefCounts.mapOiOeLimit-1;
OiRange: TYPE = [RTRefCounts.mapOiOeStart..RTRefCounts.mapOiOeStart+OverflowSize);
GraphData: TYPE = REF GraphDataRec;
GraphEntry: TYPE = RECORD [
value: INTEGER ← 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: Labels.Label,
value,increment,lo,hi: INT];
**** The following stats can be examined by the interpreter ****
WatchStats: TYPE = RECORD [
diskFree, gfiFree, mdsFree: INT ← 0,
vmFree, vmRun: INT ← 0,
vmCedar, vmZones: INT ← 0,
seconds: INT ← 0,
cpuLoad: REAL ← 0.0
];
watchStats: WatchStats;
**** Global variables for Watch ****
cifsLabel: Labels.Label ← NIL;
cifsChanges: LIST OF Rope.ROPENIL;
cifsPause: CONDITION ← [timeout: Process.SecondsToTicks[1]];
cifsWait: CONDITION;
idleCount: INT ← 0;
quit: BOOLEANFALSE; -- watched by various processes for exit notification
waitCond: CONDITION;
lastVMPoll: System.GreenwichMeanTime ← System.gmtEpoch;
newOpenHeight: INTEGER ← 0;
oldOpenHeight: INTEGER ← 0;
smallerOpenHeight: INTEGER ← 0;
biggerOpenHeight: INTEGER ← 0;
**** Procedures for Watch ****
GraphPaint: ViewerClasses.PaintProc = {
OPEN Imager;
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 => TRUSTED {
SetColor[context, black];
MaskIntRectangle[context, [0, 2*self.ch/3, data.top.value, self.ch/3]]};
smaller => {
SetColor[context, white];
MaskIntRectangle[context, [data.top.value, 2*self.ch/3, self.cw, self.ch/3]]};
ENDCASE;
SELECT data.middle.updateHint FROM
bigger => TRUSTED {
SetColor[context, black];
MaskIntRectangle[context, [0, self.ch/3, data.middle.value, self.ch/3]]};
smaller => {
SetColor[context, white];
MaskIntRectangle[context, [data.middle.value, self.ch/3, self.cw, self.ch/3]]};
ENDCASE;
SELECT data.bottom.updateHint FROM
bigger => TRUSTED {
SetColor[context, black];
MaskIntRectangle[context, [0, 0, data.bottom.value, self.ch/3]]};
smaller => {
SetColor[context, white];
MaskIntRectangle[context, [data.bottom.value, 0, self.cw, self.ch/3]]};
ENDCASE;
};
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*t + 0.36415*(t*t*t)
};
GraphSet: PROC [graph: ViewerClasses.Viewer, t, m, b: REAL] = {
tempR: REAL;
tempI: INTEGER;
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;
tempI ← Real.RoundI[tempR];
myData.top.updateHint ← IF tempI > myData.top.value THEN bigger
ELSE IF tempI < myData.top.value THEN smaller ELSE same;
myData.top.value ← tempI;
tempR ← IF myData.middle.fullScale<0 THEN m*graph.cw
ELSE Log10[1 + m]*graph.cw/myData.middle.fullScale;
tempI ← Real.RoundI[tempR];
myData.middle.updateHint ← IF tempI > myData.middle.value THEN bigger
ELSE IF tempI < myData.middle.value THEN smaller ELSE same;
myData.middle.value ← tempI;
tempR ← IF myData.bottom.fullScale<0 THEN b*graph.cw
ELSE Log10[1 + b]*graph.cw/myData.bottom.fullScale;
tempI ← Real.RoundI[tempR];
myData.bottom.updateHint ← IF tempI > myData.bottom.value THEN bigger
ELSE IF tempI < myData.bottom.value THEN smaller ELSE same;
myData.bottom.value ← tempI;
IF NOT graph.parent.iconic THEN
TRUSTED{ ViewerOps.PaintViewer[graph, client, FALSE, $Update] }
};
Decimal: PROC [int: INT] RETURNS [ROPE] = {
SELECT int FROM
0 => RETURN ["0"];
1 => RETURN ["1"];
2 => RETURN ["2"];
3 => RETURN ["3"];
ENDCASE;
RETURN[Convert.ValueToRope[[signed[int, 10]]]]};
AdjustParameter: Buttons.ButtonProc = {
[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, paint: BOOLTRUE] = {
IF NOT quit THEN {
IF label.destroyed THEN RETURN;
NumberLabels.NumberLabelUpdate[label, value];
};
};
UpdateCifsStatus: ENTRY PROC [r: ROPE] = TRUSTED {
prev: LIST OF Rope.ROPENIL;
IF quit THEN RETURN;
FOR this: LIST OF Rope.ROPE ← cifsChanges, this.rest UNTIL this = NIL DO
prev ← this;
ENDLOOP;
IF prev = NIL
THEN cifsChanges ← CONS[first: r, rest: NIL]
ELSE prev.rest ← CONS[first: r, rest: NIL];
NOTIFY cifsWait;
};
NextCifsStatus: ENTRY PROC RETURNS [r: ROPE] = TRUSTED {
WAIT cifsPause; -- force one second between display changes --
WHILE cifsChanges = NIL AND NOT quit DO WAIT cifsWait ENDLOOP;
IF quit THEN {cifsChanges ← NIL; RETURN};
r ← cifsChanges.first; cifsChanges ← cifsChanges.rest;
};
FindPosAfter: PROC [base: ROPE, object: ROPE, pos: INT ← 0] RETURNS [INT] = TRUSTED {
start: INT ← Rope.Find[s1: base, pos1: pos, s2: object, case: FALSE];
IF start < 0 THEN RETURN [pos];
RETURN [start + object.Length[]];
};
DisplayCifsStatus: PROC = TRUSTED {
UNTIL quit DO
r: ROPE ← NextCifsStatus[];
IF r.Length[] = 0
THEN Labels.SetDisplayStyle[cifsLabel, $BlackOnWhite]
ELSE {
pos: INT ← FindPosAfter[r, "Retrieving "];
IF pos # 0 THEN
r ← r.Replace[0, FindPosAfter[r, "/Indigo/", pos], "Cifs: "];
Labels.Set[cifsLabel, r];
Labels.SetDisplayStyle[cifsLabel, $WhiteOnBlack];
};
ENDLOOP;
cifsLabel ← NIL;
cifsChanges ← NIL;
};
IdleProcess: PROC = TRUSTED {
Process.SetPriority[0];
WHILE NOT quit DO idleCount ← idleCount + 1; ENDLOOP;
};
SumZones: PROC RETURNS [inZones: CARDINAL ← 0] = TRUSTED {
map: RTZones.TMapQZf ← RTZones.MapQZf;
FOR i: NAT IN [0..map.length) DO
f: RTZones.ZoneFinger ← map[i];
IF f = RTZones.mzVacant THEN LOOP;
inZones ← inZones + RTQuanta.PagesPerQuantum;
ENDLOOP;
};
SameFile: PROC [cap1,cap2: File.Capability] RETURNS [BOOL] = TRUSTED {
RETURN [File.ShowCapability[cap1].fID = File.ShowCapability[cap2].fID];
};
FileFromAddress: PROC [addr: LONG POINTER] RETURNS [File.Capability] = TRUSTED {
ENABLE {ABORTED => GO TO abort; ANY => GO TO none};
page: Space.PageNumber ← Space.PageFromLongPointer[addr];
space: Space.Handle ← Space.GetHandle[page];
RETURN [Space.GetWindow[space].file];
EXITS abort => ERROR ABORTED; none => RETURN [File.nullCapability];
};
CountVM: PROC [parent: Space.Handle] RETURNS [
total--pages in use--,
largest--largest leaf space--,
avg--size of leaf spaces--,
count--number of leaf spaces--,
totalFree--number of free pages--,
largestFree--largest free run--,
avgFree--size of free run--,
countFree--number of free runs--,
mdsFree--number of MDS pages free--,
cedarPages--pages mapped to Cedar backing file--,
mappedPages--pages mapped to some backing file--,
unmappedPages--pages in allocated spaces, but not mapped--,
anonPages--pages mapped to anonymous backing file--,
unknownPages--pages mapped to some file, but not known which file--,
sparePages--spare pages within low-level spaces--,
toBoot--pages mapped to boot file--: INT] = {
accumForSpace: PROC
[space: Space.Handle, level: INTEGER ← 0, tryMapped: BOOLTRUE] = TRUSTED {
child: Space.Handle;
spaceBase, spaceLim, nextStart, pages: CARDINAL;
mapped: BOOL;
[lowestChild: child, size: pages, base: spaceBase, mapped: mapped] ←
Space.GetAttributes[space];
nextStart ← 0;
spaceLim ← spaceBase + pages;
IF tryMapped AND mapped THEN TRUSTED {
Find out if it is for the Cedar VM file.
file: File.Capability ← File.nullCapability;
mappedPages ← mappedPages + pages;
{ENABLE {ABORTED => GO TO abort; ANY => GO TO noGood};
file ← Space.GetWindow[space].file;
EXITS
noGood => unknownPages ← unknownPages + pages;
abort => ERROR ABORTED;
};
SELECT TRUE FROM
SameFile[file, CedarVMFile] =>
cedarPages ← cedarPages + pages;
SameFile[file, File.nullCapability] =>
anonPages ← anonPages + pages;
SameFile[bootFile, File.nullCapability] => {
We believe that the boot file is the first we see.
bootFile ← file;
toBoot ← toBoot + pages};
SameFile[file, bootFile] => {
toBoot ← toBoot + pages};
ENDCASE;
};
IF NOT mapped AND child # Space.nullHandle THEN {
childSize: CARDINAL ← 0;
nextChild: Space.Handle;
base: CARDINAL;
[] ← Space.GetAttributes[child ! ANY => GO TO forceLeaf];
WHILE child # Space.nullHandle DO
[nextSibling: nextChild, size: childSize, base: base]
← Space.GetAttributes[child ! ANY => GO TO moreUnknown];
IF base > nextStart THEN {
we just passed over a free run of VM pages
free: CARDINAL ← base-nextStart;
IF level > 1
THEN
IF space = Space.mds
THEN mdsFree ← mdsFree + free
ELSE sparePages ← sparePages + free
ELSE {
totalFree ← totalFree + free;
countFree ← countFree + 1;
IF free > largestFree THEN largestFree ← free};
};
nextStart ← base + childSize;
accumForSpace[child, level+1, tryMapped AND NOT mapped];
child ← nextChild;
ENDLOOP;
base ← pages;
IF base > nextStart THEN {
the last run of VM pages was free
free: CARDINAL ← base-nextStart;
IF level > 1
THEN
IF space = Space.mds
THEN mdsFree ← mdsFree + free
ELSE sparePages ← sparePages + free
ELSE {
totalFree ← totalFree + free;
countFree ← countFree + 1;
IF free > largestFree THEN largestFree ← free};
};
RETURN;
EXITS
forceLeaf => {};
moreUnknown => {
unknownPages ← unknownPages + pages - (nextStart-spaceBase);
RETURN;
};
};
At this point we have a leaf space, so add it to the counts.
total ← total + pages;
count ← count + 1;
IF largest < pages THEN largest ← pages;
IF tryMapped AND NOT mapped THEN
unmappedPages ← unmappedPages + pages;
};
bootFile: File.Capability ← File.nullCapability;
CedarVMFile: File.Capability ← File.nullCapability;
TRUSTED {
rope: ROPE ← "rope";
CedarVMFile ← FileFromAddress[LOOPHOLE[rope]];
bootFile ← FileFromAddress[LOOPHOLE[DiskDriverSharedImpl,POINTER]];
};
FOR i: NAT IN [0..10) DO
total ← largest ← avg ← count ← totalFree ← largestFree ← avgFree ← countFree ← cedarPages ← mappedPages ← unmappedPages ← anonPages ← unknownPages ← sparePages ← toBoot ← mdsFree ← 0;
accumForSpace[parent, 1 ! ABORTED => GO TO abort; ANY => LOOP];
EXIT;
ENDLOOP;
IF count > 0 THEN avg ← total / count;
IF countFree > 0 THEN avgFree ← totalFree / countFree;
EXITS abort => ERROR ABORTED;
};
CountGFI: PROC RETURNS [free: INT] = TRUSTED {
free ← 0;
FOR i: CARDINAL DECREASING IN [1..SDDefs.SD[SDDefs.sGFTLength]) DO
item: PrincOpsRuntime.GFTItem ← PrincOpsRuntime.GFT[i];
IF item.data # 0 THEN EXIT;
free ← free + 1;
ENDLOOP;
};
GetRCTabStats: PROC
RETURNS [
normal, pinned, finalize, chains, normalFree, overflow, overflowFree: INTEGER ← 0]
= TRUSTED {
mapPiRce: LONG POINTER TO RTRefCounts.AMapPiRce = RTRefCounts.GetMapPiRce[];
rPtr: LONG POINTER TO RTRefCounts.RCEntry ← @mapPiRce[0];
mapOiOe: LONG POINTER TO RTRefCounts.AMapOiOe = RTRefCounts.GetMapOiOe[];
oPtr: LONG POINTER TO RTRefCounts.OverflowEntry ← @mapOiOe[0];
FOR pi: CARDINAL IN [0..NormalSize) DO
entry: RTRefCounts.RCEntry = rPtr^;
rPtr ← rPtr + SIZE[RTRefCounts.RCEntry];
IF entry = RTRefCounts.rceEmpty THEN {normalFree ← normalFree + 1; LOOP};
WITH e: entry SELECT FROM
overflow =>
chains ← chains + 1;
normal => {
normal ← normal + 1;
SELECT e.rc FROM
LAST[RTRefCounts.RefCt] => pinned ← pinned + 1;
<= RTRefCounts.rcFinalize => finalize ← finalize + 1;
ENDCASE;
};
ENDCASE;
ENDLOOP;
FOR oi: CARDINAL IN OiRange DO
oe: RTRefCounts.OverflowEntry = oPtr^;
rce: RTRefCounts.RCEntry = oe.rce;
oPtr ← oPtr + SIZE[RTRefCounts.OverflowEntry];
WITH e: rce SELECT FROM
overflow =>
overflowFree ← overflowFree + 1;
normal => {
overflow ← overflow + 1;
SELECT e.rc FROM
LAST[RTRefCounts.RefCt] => pinned ← pinned + 1;
<= RTRefCounts.rcFinalize => finalize ← finalize + 1;
ENDCASE;
};
ENDCASE;
ENDLOOP;
};
CountDisk: PROC RETURNS [free: INT] = {
[freePageCount: free] ← Volume.GetAttributes[Volume.SystemID[]];
};
CauseGCHit: Buttons.ButtonProc = 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 # red];
IF shift THEN SafeStorage.TrimAllZones[];
IF control THEN [] ← SafeStorage.TrimRootBase[];
IF control OR shift THEN ForceSampleEntry[];
Buttons.SetDisplayStyle[viewer, $BlackOnWhite];
};
};
CollectorWatcher: PROC [gcStatusLabel: Labels.Label] = {
ENABLE ABORTED => GO TO done;
active: ROPE = "BUSY: ";
trimming: ROPE = "BUSY: trimming zones";
inactive: ROPE = "done.";
wordsBetweenTrim: INT ← 0;
defaultBetweenTrim: INT ← UserProfile.Number["Watch.BetweenTrim", 256*LONG[1024]];
defaultCedarDelta: INT ← UserProfile.Number["Watch.CedarDelta", 1600];
WHILE NOT quit DO
cReasons: ARRAY SafeStorage.ReclamationReason OF ROPE =
["clientRequest", "clientTandSRequest", "clientNoTraceRequest", "rcTableOverflow",
"allocationInterval", "quantaNeeded", "finalizationThreshold"];
r: SafeStorage.ReclamationReason;
wrds,objs: LONG CARDINAL;
GCnumber: CARDINAL;
msg: ROPE ← inactive;
[reason: r] ← SafeStorage.WaitForCollectorStart[];
IF quit THEN EXIT;
TRUSTED{ Labels.Set[gcStatusLabel, active.Concat[cReasons[r]] ] };
[incarnation: GCnumber, reason: r, wordsReclaimed: wrds, objectsReclaimed: objs] ←
SafeStorage.WaitForCollectorDone[];
wordsBetweenTrim ← wordsBetweenTrim + wrds;
IF quit THEN EXIT;
IF watchStats.vmRun < 1000
AND watchStats.vmCedar - watchStats.vmZones > defaultCedarDelta
AND defaultBetweenTrim > 0
AND wordsBetweenTrim > defaultBetweenTrim THEN {
Labels.Set[gcStatusLabel, trimming];
wordsBetweenTrim ← 0;
SafeStorage.TrimAllZones[];
[] ← SafeStorage.TrimRootBase[];
};
TRUSTED{
IF quit THEN EXIT;
Labels.Set[
gcStatusLabel,
inactive.Cat[
" (GC#",
Decimal[GCnumber],
" got ",
Decimal[LOOPHOLE[wrds]].Concat[" words, "],
Decimal[LOOPHOLE[objs]].Concat[" objs)"]]];
};
ENDLOOP;
EXITS
done => {};
};
SetGCInt: ENTRY PROC [parm: Parameter] = {
ENABLE UNWIND => NULL;
[] ← SafeStorage.SetCollectionInterval[parm.value];
};
MyDestroy: ENTRY ViewerClasses.DestroyProc = {
ENABLE UNWIND => NULL;
viewer: ViewerClasses.Viewer ← NARROW[self];
quit ← TRUE;
BROADCAST waitCond;
};
WaitForUpdate: ENTRY PROC RETURNS[ vmWanted: BOOL ] = {
ENABLE UNWIND => NULL;
now: System.GreenwichMeanTime;
vmInterval: INT ← 60; -- seconds --
interval: INT;
WAIT waitCond;
now ← System.GetGreenwichMeanTime[];
interval ← now - lastVMPoll;
vmWanted ← interval > vmInterval;
watchStats.seconds ← watchStats.seconds + interval;
};
SetPause: ENTRY PROC [parm: Parameter] = TRUSTED {
ENABLE UNWIND => NULL;
Process.SetTimeout[@waitCond, Process.SecondsToTicks[parm.value]];
BROADCAST waitCond;
};
ForceSample: Buttons.ButtonProc = {
[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;
lastVMPoll ← System.gmtEpoch;
BROADCAST waitCond;
};
Watcher: PROC = TRUSTED {
ENABLE ABORTED => GO TO done;
defaultInterval: INT ← UserProfile.Number["Watch.GCInterval", 16000];
defaultSample: INT ← UserProfile.Number["Watch.SamplePause", 2];
graphClass: ViewerClasses.ViewerClass =
NEW[ViewerClasses.ViewerClassRec ← [paint: GraphPaint, destroy: MyDestroy]];
container, graph: ViewerClasses.Viewer ← NIL;
wordsLabel, faultLabel, diskReqLabel, mdsLabel, gfiLabel, diskLabel: Labels.Label ← NIL;
freeVMLabel, maxFreeLabel: Labels.Label ← NIL;
cedarVMLabel, inZonesLabel: Labels.Label ← NIL;
mappedPagesLabel,unmappedPagesLabel: Labels.Label ← NIL;
unmappedFullLabel,unmappedPartLabel,unmappedMDSLabel: Labels.Label ← NIL;
anonPagesLabel,toBootLabel,spareLabel: Labels.Label ← NIL;
usedNormalLabel,finalizeNormalLabel: Labels.Label ← NIL;
pinnedNormalLabel,freeNormalLabel: Labels.Label ← NIL;
usedOverflowLabel,chainsOverflowLabel,freeOverflowLabel: Labels.Label ← NIL;
gapX: INTEGER = 2;
gapY: INTEGER = 2;
lastX: INTEGER;
nextX: INTEGER ← gapX;
nextY: INTEGER ← 0;
faults, faultRate, oldFaultRate, deltaFaults: INT ← 0;
words, wordsRate, oldWordsRate, deltaWords: INT ← 0;
maxIdleRate: INT ← 1;
oldIdleRate, idleRate, lastIdle: INT ← 0;
delta, mark, deltaMillis: ShowTime.Microseconds ← 0;
vmWanted: BOOLTRUE; -- whether to sample VM stats this time --
newMDS, oldMDS: INT ← 0;
newGFI, oldGFI: INT ← 0;
unknownPages: INT ← 0;
newDiskReq: INT ← 0;
newDisk: INT ← 0;
freeVM: INT ← 0;
maxFreeVM: INT ← 0;
cedarVM: INT ← 0;
inZones: INT ← 0;
mappedPages: INT ← 0;
unmappedPages: INT ← 0;
unmappedFullPages: INT ← 0;
unmappedPartPages: INT ← 0;
anonPages: INT ← 0;
toBoot: INT ← 0;
usedNormal: INTEGER ← 0;
finalizeNormal: INTEGER ← 0;
pinnedNormal: INTEGER ← 0;
freeNormal: INTEGER ← 0;
usedOverflow: INTEGER ← 0;
chainsOverflow: INTEGER ← 0;
freeOverflow: INTEGER ← 0;
AddLabel: PROC
[prefix: ROPE, chars: NAT ← 0, lastInLine: BOOLFALSE, ww: INTEGER ← 0]
RETURNS [label: Labels.Label] = TRUSTED {
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};
};
AddButton: PROC
[buttonName: ROPENIL, chars: NAT ← 0, parm: Parameter ← NIL, proc: Buttons.ButtonProc ← NIL, lastInLine: BOOLFALSE]
RETURNS [button: Buttons.Button] = TRUSTED {
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};
};
CreateGraph: PROC RETURNS[viewer: ViewerClasses.Viewer] = TRUSTED {
W2: PROC [r: ROPE] RETURNS [INTEGER] = TRUSTED INLINE
{RETURN[VFonts.RopeWidth[r]/2]};
xTemp: CARDINAL;
wordsLabel ← AddLabel["Words", 9, TRUE]; xTemp ← lastX;
[] ← AddLabel["CPU Load ", 0, TRUE]; xTemp ← MAX[xTemp, lastX];
faultLabel ← AddLabel["Faults", 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: faultLabel.wy - (wordsLabel.wy + wordsLabel.wh),
data: NEW[GraphDataRec ← [[fullScale: 5], [fullScale: -1], [fullScale: 2]]]],
paint: FALSE];
xTemp ← viewer.ww/5;
[] ← Labels.Create
[info: [name: "1", parent: container, wx: viewer.wx - W2["1"],
wy: wordsLabel.wy, border: FALSE], paint: FALSE];
[] ← Labels.Create
[info: [name: "10", parent: container, wx: viewer.wx + xTemp - W2["10"],
wy: wordsLabel.wy, border: FALSE], paint: FALSE];
[] ← Labels.Create
[info: [name: "100", parent: container, wx: viewer.wx + 2*xTemp - W2["100"],
wy: wordsLabel.wy, border: FALSE], paint: FALSE];
[] ← Labels.Create
[info: [name: "1000", parent: container, wx: viewer.wx + 3*xTemp - W2["1000"],
wy: wordsLabel.wy, border: FALSE], paint: FALSE];
[] ← Labels.Create
[info: [name: "10000", parent: container, wx: viewer.wx + 4*xTemp - W2["10000"],
wy: wordsLabel.wy, border: FALSE], paint: FALSE];
xTemp ← viewer.ww/4;
[] ← Labels.Create
[info: [name: "1", parent: container, wx: viewer.wx - W2["1"],
wy: faultLabel.wy, border: FALSE], paint: FALSE];
[] ← Labels.Create
[info: [name: "3", parent: container, wx: viewer.wx + xTemp - W2["3"],
wy: faultLabel.wy, border: FALSE], paint: FALSE];
[] ← Labels.Create
[info: [name: "10", parent: container, wx: viewer.wx + xTemp*2 - W2["10"],
wy: faultLabel.wy, border: FALSE], paint: FALSE];
[] ← Labels.Create
[info: [name: "30", parent: container, wx: viewer.wx + xTemp*3 - W2["30"],
wy: faultLabel.wy, border: FALSE], paint: FALSE];
}; --CreateGraph--
pauseParm: Parameter ←
NEW[ParameterBlock ← [SetPause, NIL, defaultSample, 0, 1, 256]];
gcParm: Parameter ←
NEW[ParameterBlock ← [SetGCInt, NIL, defaultInterval, 0, 1000, 1024000]];
WHILE NOT Runtime.IsBound[NumberLabels.CreateNumber] DO
Process.Pause[Process.SecondsToTicks[1]];
ENDLOOP;
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
diskReqLabel ← AddLabel["requests", 7];
diskLabel ← AddLabel["disk", 5];
gfiLabel ← AddLabel["gfi", 3];
mdsLabel ← AddLabel["mds", 2];
freeVMLabel ← AddLabel["VM", 4];
maxFreeLabel ← AddLabel["VM run", 4, TRUE];
line 2: gc interval, button & status
[] ← AddButton[buttonName: "GC interval", chars: 7, parm: gcParm];
SetLabel[gcParm.label, gcParm.value];
SafeStorage.TrimAllZones[];
SetGCInt[gcParm];
[] ← AddButton[buttonName: "GC", proc: CauseGCHit];
{
gcStatusLabel: Labels.Label = AddLabel
["inactive.", 0, TRUE, ViewerSpecs.openRightWidth];
Containers.ChildXBound[container, gcStatusLabel];
Process.Detach[FORK CollectorWatcher[gcStatusLabel]];
};
line 3: sample button and CIFS status
[] ← AddButton[buttonName: "Sample", proc: ForceSample];
[] ← AddButton[buttonName: "interval", chars: 3, parm: pauseParm];
SetLabel[pauseParm.label, pauseParm.value];
SetPause[pauseParm];
cifsLabel ←
AddLabel["CIFS status (inverted iff active)", 0, TRUE, ViewerSpecs.openRightWidth];
Containers.ChildXBound[container, cifsLabel];
CIFSFeedback.Register[UpdateCifsStatus];
Process.Detach[FORK DisplayCifsStatus];
set an aesthetically sized open window
newOpenHeight ← oldOpenHeight ← smallerOpenHeight ← nextY + gapY - 1;
ViewerOps.SetOpenHeight[container, oldOpenHeight];
line 4: other mapped VM stuff
mappedPagesLabel ← AddLabel["mapped", 5];
toBootLabel ← AddLabel["boot", 4];
anonPagesLabel ← AddLabel["anon", 4];
cedarVMLabel ← AddLabel["Cedar", 4];
inZonesLabel ← AddLabel["zones", 4, TRUE];
line 5: unmapped VM stuff
unmappedPagesLabel ← AddLabel["unmapped", 4];
unmappedFullLabel ← AddLabel["full", 4];
unmappedPartLabel ← AddLabel["part", 4];
unmappedMDSLabel ← AddLabel["mds", 2, TRUE];
line 6: RC table stats
usedNormalLabel ← AddLabel["normalRC", 5];
finalizeNormalLabel ← AddLabel["finalize", 4];
pinnedNormalLabel ← AddLabel["pinned", 4];
freeNormalLabel ← AddLabel["free", 5, TRUE];
line 7: RC table stats
usedOverflowLabel ← AddLabel["overflowRC", 5];
chainsOverflowLabel ← AddLabel["chains", 4];
freeOverflowLabel ← AddLabel["free", 5, TRUE];
biggerOpenHeight ← nextY + gapY - 1;
initialize measurments
mark ← ShowTime.GetMark[];
idleCount ← 0;
Process.Detach[FORK IdleProcess[]];
RTProcess.StartWatchingFaults[];
faults ← RTProcess.GetTotalPageFaults[];
words ← SafeStorage.NWordsAllocated[];
WHILE NOT quit AND NOT container.destroyed DO
Get the time delta
delta ← ShowTime.GetMark[] - mark;
deltaMillis ← (delta + 500) / 1000;
IF deltaMillis = 0 THEN LOOP;
mark ← mark + delta;
Update the idle rate data
idleRate ← (idleCount-lastIdle) / deltaMillis;
lastIdle ← idleCount;
IF idleRate > maxIdleRate THEN
{ IF deltaMillis > 100 THEN maxIdleRate ← idleRate ELSE idleRate ← maxIdleRate };
Update the fault data
deltaFaults ← RTProcess.GetTotalPageFaults[] - faults;
faults ← faults + deltaFaults;
faultRate ← (deltaFaults * 1000 + 500) / deltaMillis;
Update the alloc data
deltaWords ← SafeStorage.NWordsAllocated[] - words;
words ← words + deltaWords;
wordsRate ← (deltaWords * 1000 + 500) / deltaMillis;
IF container.iconic THEN {
The user really does not want to see the data now. We performed the above calculations just so we don't overflow various numbers if the user keeps this tool iconic for a long time.
Process.Pause[Process.SecondsToTicks[1]];
LOOP;
};
Sample the new numbers
newGFI ← CountGFI[];
newDiskReq ← DiskDriverSharedImpl.totalNumberOfRequests;
newDisk ← CountDisk[];
IF vmWanted AND NOT container.iconic THEN {
Calculate the stats for the VM usage (but only if they will be displayed)
[totalFree: freeVM, largestFree: maxFreeVM, mdsFree: newMDS, cedarPages: cedarVM, mappedPages: mappedPages, unmappedPages: unmappedFullPages, anonPages: anonPages, unknownPages: unknownPages, sparePages: unmappedPartPages, toBoot: toBoot] ← CountVM[Space.virtualMemory];
inZones ← SumZones[];
We also try to get RC table stats here, but only if they will be seen!
IF newOpenHeight > smallerOpenHeight THEN
[usedNormal, pinnedNormal, finalizeNormal, chainsOverflow, freeNormal, usedOverflow, freeOverflow] ← GetRCTabStats[];
lastVMPoll ← System.GetGreenwichMeanTime[];
};
IF newOpenHeight # oldOpenHeight THEN {
ViewerOps.SetOpenHeight[container, oldOpenHeight ← newOpenHeight];
IF NOT container.iconic THEN
ViewerOps.ComputeColumn[ViewerOps.ViewerColumn[container]];
};
Update the display (only where necessary)
IF quit OR container.destroyed THEN RETURN;
SetLabel[diskReqLabel, newDiskReq];
SetLabel[diskLabel, watchStats.diskFree ← newDisk];
SetLabel[mdsLabel, watchStats.mdsFree ← newMDS];
SetLabel[unmappedMDSLabel, newMDS];
SetLabel[gfiLabel, watchStats.gfiFree ← oldGFI ← newGFI];
SetLabel[wordsLabel, words];
SetLabel[faultLabel, faults];
IF oldFaultRate # faultRate
OR oldWordsRate # wordsRate
OR oldIdleRate # idleRate THEN
GraphSet[
graph,
oldWordsRate ← wordsRate,
watchStats.cpuLoad ← 1 - (oldIdleRate ← idleRate)/(maxIdleRate*1.0),
oldFaultRate ← faultRate];
SetLabel[freeVMLabel, watchStats.vmFree ← freeVM];
SetLabel[maxFreeLabel, watchStats.vmRun ← maxFreeVM];
SetLabel[inZonesLabel, watchStats.vmZones ← inZones];
SetLabel[cedarVMLabel, watchStats.vmCedar ← cedarVM];
SetLabel[mappedPagesLabel, mappedPages];
SetLabel[unmappedFullLabel, unmappedFullPages];
SetLabel[unmappedPartLabel, unmappedPartPages];
unmappedPages ← unmappedFullPages + unmappedPartPages + newMDS;
SetLabel[unmappedPagesLabel, unmappedPages];
SetLabel[anonPagesLabel, anonPages];
SetLabel[toBootLabel, toBoot];
SetLabel[usedNormalLabel, usedNormal];
SetLabel[finalizeNormalLabel, finalizeNormal];
SetLabel[pinnedNormalLabel, pinnedNormal];
SetLabel[freeNormalLabel, freeNormal];
SetLabel[usedOverflowLabel, usedOverflow];
SetLabel[chainsOverflowLabel, chainsOverflow];
SetLabel[freeOverflowLabel, freeOverflow];
Lastly, wait for the pause interval
vmWanted ← WaitForUpdate[];
ENDLOOP;
quit ← TRUE;
RTProcess.StopWatchingFaults[];
EXITS
done => {RTProcess.StopWatchingFaults[]};
};
**** Initialization code ****
TRUSTED {
Process.Detach[FORK Watcher];
};
END.