Watch.mesa
Andrew Birrell, November 12, 1982 10:07 am
Paul Rovner, March 1, 1983 5:21 pm
Russ Atkinson, December 12, 1983 6:17 pm
DIRECTORY
BasicTime USING [GetClockPulses, Pulses, PulsesToMicroseconds],
Buttons USING [Button, ButtonProc, Create, SetDisplayStyle],
Containers USING [ChildXBound, Create],
Convert USING [RopeFromInt],
DebuggerSwap USING [WorryCallDebugger],
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],
Menus USING [MenuProc],
NumberLabels USING [CreateNumber, NumberLabel, NumberLabelUpdate],
PrincOps USING [GFT, GFTItem, PageCount, SD, sGFTLength],
Process USING [Detach, MsecToTicks, Pause, SecondsToTicks, SetPriority, SetTimeout],
Real USING [RoundI],
Rope USING [Cat, Concat, Find, Length, ROPE, Substr],
SafeStorage USING [
NWordsAllocated, ReclaimCollectibleObjects, ReclamationReason, SetCollectionInterval, WaitForCollectorDone, WaitForCollectorStart],
UserProfile USING [Number],
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, Convert, DebuggerSwap, Disk, File, FileStats, FSExtras, Graphics, Labels, NumberLabels, Process, 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;
newOpenHeight: INTEGER ← 0;
oldOpenHeight: INTEGER ← 0;
smallerOpenHeight: INTEGER ← 0;
biggerOpenHeight: INTEGER ← 0;
defaultInterval: INT ← UserProfile.Number["Watch.GCInterval", 16000];
defaultSample: INT ← UserProfile.Number["Watch.SamplePause", 2];
longPause: INT ← UserProfile.Number["Watch.LongPause", 30] * 1000;
millisSinceLastBigBang: INT ← longPause;
**** 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*t + 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];
}
};
Decimal: PROC [int: INT] RETURNS [ROPE] = {
SELECT int FROM
0 => RETURN ["0"];
1 => RETURN ["1"];
2 => RETURN ["2"];
3 => RETURN ["3"];
ENDCASE;
RETURN[Convert.RopeFromInt[int]]};
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, paint: BOOLTRUE] = {
IF NOT quit THEN {
IF label.destroyed THEN RETURN;
NumberLabels.NumberLabelUpdate[label, value];
};
};
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[]];
};
DisplayFSStatus: PROC
[readFSlabel, writeFSlabel, flushFSlabel, NreadFSlabel, NwriteFSlabel, NflushFSlabel: Label]
= {
WaitForFSUpdate: ENTRY PROC = {WAIT fsPause};
fsPause: CONDITION ← [timeout: Process.MsecToTicks[200]];
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, "[Indigo]"]]];
Labels.SetDisplayStyle[nameLabel, style];
SetLabel[countLabel, count];
ENDLOOP;
};
IdleProcess: PROC = TRUSTED {
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 {
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;
};
CountFreePages: PROC RETURNS [INT] = {
RETURN[File.GetVolumePages[File.SystemVolume[]].free];
};
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 =
["clientRequest", "clientTandSRequest", "clientNoTraceRequest", "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,
inactive.Cat[
" (GC#",
Decimal[GCnumber],
" got ",
Decimal[LOOPHOLE[wrds]].Concat[" words, "],
Decimal[LOOPHOLE[objs]].Concat[" 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 RETURNS [vmWanted: BOOL] = {
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;
};
Watcher: PROC = TRUSTED {
ENABLE ABORTED => GO TO done;
graphClass: ViewerClasses.ViewerClass =
NEW[ViewerClasses.ViewerClassRec ← [paint: GraphPaint, destroy: MyDestroy]];
container, graph: ViewerClasses.Viewer ← NIL;
wordsLabel, diskIOLabel: Label ← NIL;
Free line labels
diskLabel, gfiLabel, mdsLabel, freeVMLabel, maxFreeLabel: 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
fsOpenCallsLabel, fsOpenPagesLabel, fsOpenMSecsLabel: Label ← NIL;
fsCreateCallsLabel, fsCreatePagesLabel, fsCreateMSecsLabel: Label ← NIL;
fsDeleteCallsLabel, fsDeletePagesLabel, fsDeleteMSecsLabel: Label ← NIL;
fsExtendCallsLabel, fsExtendPagesLabel, fsExtendMSecsLabel: Label ← NIL;
fsContractCallsLabel, fsContractPagesLabel, fsContractMSecsLabel: Label ← NIL;
fsReadCallsLabel, fsReadPagesLabel, fsReadMSecsLabel: Label ← NIL;
fsWriteCallsLabel, fsWritePagesLabel, fsWriteMSecsLabel: Label ← NIL;
gapX: INTEGER = 2;
gapY: INTEGER = 2;
lastX: INTEGER;
nextX: INTEGER ← gapX;
nextY: INTEGER ← 0;
diskIORate, diskPercent, oldDiskPercent: REAL ← 0.0;
diskIO: INT ← 0;
words, wordsRate, oldWordsRate, deltaWords: INT ← 0;
oldActiveDiskPulses, oldTotalDiskPulses: BasicTime.Pulses ← 0;
maxIdleRate: INT ← 1;
oldIdleRate, idleRate, lastIdle: INT ← 0;
mark: BasicTime.Pulses;
delta, deltaMillis: LONG CARDINAL ← 0;
vmWanted: BOOLTRUE; -- whether to sample VM stats this time --
newMDS, oldMDS: INT ← 0;
newGFI, oldGFI: INT ← 0;
freeVM: INT ← 0;
maxFreeVM: INT ← 0;
AddLabel: PROC
[prefix: ROPE, chars: NAT ← 0, lastInLine: BOOLFALSE, ww: INTEGER ← 0]
RETURNS [label: 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.StringWidth[r]/2];
};
xTemp: CARDINAL;
tempLabel: Labels.Label;
wordsLabel ← AddLabel["Words", 9, TRUE]; xTemp ← lastX;
[] ← AddLabel["CPU Load ", 0, 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;
[] ← 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;
tempLabel ← Labels.Create
[info: [name: "0%", parent: container, wx: viewer.wx - W2["1"],
wy: diskIOLabel.wy, border: FALSE], paint: FALSE];
tempLabel ← Labels.Create
[info: [name: "25%", parent: container, wx: viewer.wx + xTemp - W2["3"],
wy: diskIOLabel.wy, border: FALSE], paint: FALSE];
tempLabel ← Labels.Create
[info: [name: "50%", parent: container, wx: viewer.wx + xTemp*2 - W2["10"],
wy: diskIOLabel.wy, border: FALSE], paint: FALSE];
tempLabel ← Labels.Create
[info: [name: "75%", parent: container, wx: viewer.wx + xTemp*3 - W2["30"],
wy: diskIOLabel.wy, border: FALSE], paint: FALSE];
}; --CreateGraph--
pauseParm: Parameter ←
NEW[ParameterBlock ← [SetPause, NIL, defaultSample, 0, 1, 64]];
gcParm: Parameter ←
NEW[ParameterBlock ← [SetGCInt, NIL, defaultInterval, 0, 1000, 512000]];
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", 5];
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];
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 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", 6];
laundryWakeupsLabel ← AddLabel["wakeups", 6];
pagesCleanedLabel ← AddLabel["pages", 7, TRUE];
panicLaundryWakeupsLabel ← AddLabel[" panic", 3];
pagesCleanedPanicLabel ← AddLabel["panicPgs", 5];
uselessLaundryWakeupsLabel ← AddLabel["useless", 6];
laundryCleanCallsLabel ← AddLabel["cleanCalls", 5, 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;
fsOpenCallsLabel ← AddLabel["calls", 6];
fsOpenPagesLabel ← AddLabel["pages", 7];
fsOpenMSecsLabel ← AddLabel["msecs", 8, TRUE];
[] ← AddLabel[" Create", 0, FALSE, ww];
fsCreateCallsLabel ← AddLabel["calls", 6];
fsCreatePagesLabel ← AddLabel["pages", 7];
fsCreateMSecsLabel ← AddLabel["msecs", 8, TRUE];
[] ← AddLabel[" Delete", 0, FALSE, ww];
fsDeleteCallsLabel ← AddLabel["calls", 6];
fsDeletePagesLabel ← AddLabel["pages", 7];
fsDeleteMSecsLabel ← AddLabel["msecs", 8, TRUE];
[] ← AddLabel[" Extend", 0, FALSE, ww];
fsExtendCallsLabel ← AddLabel["calls", 6];
fsExtendPagesLabel ← AddLabel["pages", 7];
fsExtendMSecsLabel ← AddLabel["msecs", 8, TRUE];
[] ← AddLabel[" Contract", 0, FALSE, ww];
fsContractCallsLabel ← AddLabel["calls", 6];
fsContractPagesLabel ← AddLabel["pages", 7];
fsContractMSecsLabel ← AddLabel["msecs", 8, TRUE];
[] ← AddLabel[" Read", 0, FALSE, ww];
fsReadCallsLabel ← AddLabel["calls", 6];
fsReadPagesLabel ← AddLabel["pages", 7];
fsReadMSecsLabel ← AddLabel["msecs", 8, TRUE];
[] ← AddLabel[" Write", 0, FALSE, ww];
fsWriteCallsLabel ← AddLabel["calls", 6];
fsWritePagesLabel ← AddLabel["pages", 7];
fsWriteMSecsLabel ← AddLabel["msecs", 8, TRUE];
};
biggerOpenHeight ← nextY + gapY - 1;
initialize measurments
Process.Detach[
FORK DisplayFSStatus[
readFSlabel, writeFSlabel, flushFSlabel, NreadFSlabel, NwriteFSlabel, NflushFSlabel]];
mark ← BasicTime.GetClockPulses[];
watchStats.idleCount ← 0;
Process.Detach[FORK IdleProcess[]];
words ← SafeStorage.NWordsAllocated[];
WHILE NOT quit AND NOT container.destroyed DO
Get the time delta
reads,writes,readPgs,writePgs: INT;
newActiveDiskPulses, newTotalDiskPulses: BasicTime.Pulses;
nextMark: BasicTime.Pulses = BasicTime.GetClockPulses[];
delta ← BasicTime.PulsesToMicroseconds[nextMark - mark];
deltaMillis ← (delta + 500) / 1000;
IF deltaMillis <= 1 THEN LOOP;
mark ← nextMark;
Update the idle rate data
idleRate ← (watchStats.idleCount-lastIdle) / deltaMillis;
lastIdle ← watchStats.idleCount;
IF idleRate > maxIdleRate THEN {
IF deltaMillis > 100 THEN maxIdleRate ← idleRate ELSE idleRate ← maxIdleRate;
};
Update the disk numbers
[active: newActiveDiskPulses, total: newTotalDiskPulses, reads: reads, writes: writes, readPages: readPgs, writePages: writePgs] ← Disk.GetStatistics[];
IF newTotalDiskPulses > oldTotalDiskPulses THEN
diskPercent ← (1.0*(newActiveDiskPulses-oldActiveDiskPulses))
/ (newTotalDiskPulses-oldTotalDiskPulses);
oldActiveDiskPulses ← newActiveDiskPulses;
oldTotalDiskPulses ← newTotalDiskPulses;
diskIO ← reads+writes;
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;
};
millisSinceLastBigBang ← MIN[longPause, millisSinceLastBigBang + deltaMillis];
Sample the new numbers
{pagesAllocated, pagesFreed, pagesInPartition: VM.PageCount;
[pagesAllocated, pagesFreed, pagesInPartition]
← VMStatistics.VirtualAllocation[mds];
newMDS ← MAX[0, pagesInPartition - pagesAllocated + pagesFreed];
[pagesAllocated, pagesFreed, pagesInPartition]
← VMStatistics.VirtualAllocation[normalVM];
freeVM ← MAX[0, pagesInPartition - pagesAllocated + pagesFreed];
IF millisSinceLastBigBang >= longPause AND NOT container.iconic THEN {
Calculate the stats for the VM usage (but only if they will be displayed)
millisSinceLastBigBang ← 0;
[] ← VM.Allocate[pagesInPartition+pagesInPartition
! VM.CantAllocate => {maxFreeVM ← bestInterval.count; CONTINUE};
];
Calculate the # of GFIs free
newGFI ← CountGFI[];
};
};
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;
mds, gfi, disk, freeVM
SetLabel[diskLabel, watchStats.diskFree ← CountFreePages[]];
SetLabel[mdsLabel, watchStats.mdsFree ← newMDS];
SetLabel[gfiLabel, watchStats.gfiFree ← oldGFI ← newGFI];
SetLabel[freeVMLabel, watchStats.vmFree ← freeVM];
SetLabel[maxFreeLabel, watchStats.vmRun ← maxFreeVM];
gc interval, button & status
SetLabel[wordsLabel, words];
SetLabel[diskIOLabel, diskIO];
IF oldDiskPercent # diskPercent
OR oldWordsRate # wordsRate
OR oldIdleRate # idleRate THEN
GraphSet[
graph,
oldWordsRate ← wordsRate,
watchStats.cpuLoad ← 1 - (oldIdleRate ← idleRate)/(maxIdleRate*1.0),
oldDiskPercent ← diskPercent];
IF container.wh > smallerOpenHeight THEN {
Disk stats
SetLabel[diskPercentQueuedLabel, Real.RoundI[diskPercent*100.0]];
SetLabel[
diskActiveSecsLabel,
(BasicTime.PulsesToMicroseconds[newActiveDiskPulses]+500000)/1000000];
SetLabel[
diskTotalSecsLabel,
(BasicTime.PulsesToMicroseconds[newTotalDiskPulses]+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
data: FileStats.Data;
data ← FileStats.GetData[open];
SetLabel[fsOpenCallsLabel, data.calls];
SetLabel[fsOpenPagesLabel, data.pages];
SetLabel[
fsOpenMSecsLabel, (BasicTime.PulsesToMicroseconds[data.pulses]+500)/1000];
data ← FileStats.GetData[create];
SetLabel[fsCreateCallsLabel, data.calls];
SetLabel[fsCreatePagesLabel, data.pages];
SetLabel[
fsCreateMSecsLabel, (BasicTime.PulsesToMicroseconds[data.pulses]+500)/1000];
data ← FileStats.GetData[delete];
SetLabel[fsDeleteCallsLabel, data.calls];
SetLabel[fsDeletePagesLabel, data.pages];
SetLabel[
fsDeleteMSecsLabel, (BasicTime.PulsesToMicroseconds[data.pulses]+500)/1000];
data ← FileStats.GetData[extend];
SetLabel[fsExtendCallsLabel, data.calls];
SetLabel[fsExtendPagesLabel, data.pages];
SetLabel[
fsExtendMSecsLabel, (BasicTime.PulsesToMicroseconds[data.pulses]+500)/1000];
data ← FileStats.GetData[contract];
SetLabel[fsContractCallsLabel, data.calls];
SetLabel[fsContractPagesLabel, data.pages];
SetLabel[
fsContractMSecsLabel, (BasicTime.PulsesToMicroseconds[data.pulses]+500)/1000];
data ← FileStats.GetData[read];
SetLabel[fsReadCallsLabel, data.calls];
SetLabel[fsReadPagesLabel, data.pages];
SetLabel[
fsReadMSecsLabel, (BasicTime.PulsesToMicroseconds[data.pulses]+500)/1000];
data ← FileStats.GetData[write];
SetLabel[fsWriteCallsLabel, data.calls];
SetLabel[fsWritePagesLabel, data.pages];
SetLabel[
fsWriteMSecsLabel, (BasicTime.PulsesToMicroseconds[data.pulses]+500)/1000];
};
};
Lastly, wait for the pause interval
vmWanted ← WaitForUpdate[];
ENDLOOP;
GO TO done;
EXITS done => {quit ← TRUE};
};
**** Initialization code ****
TRUSTED {
Process.Detach[FORK Watcher];
};
END.