-- Snoop.mesa; (Stolen from Watch, by McGregor on 13-Oct-81 15:59:40)
-- Edited by Paul Rovner on January 17, 1983 10:09 am

DIRECTORY
Buttons USING [Button, ButtonProc, Create],
Convert USING [IntFromRope, ValueToRope],
Inline USING [LowHalf],
Labels USING [Create, Label, Set],
Menus USING [CreateMenu, InsertMenuEntry, Menu, MenuProc, CreateEntry],
Plotter USING [Create],
Process USING [Detach, MsecToTicks, Pause, SecondsToTicks],
PSB USING [PsbIndex, PsbNull],
Real USING [FixI],
Rope USING [Concat, ROPE, Fetch],
RealEvent USING [Object, StreamHandle, CreateTimerDrivenStream, CreateEventDrivenStream],
RealVec USING [Handle, All, SortOrder, Equal],
-- RTBases USING [GetDQIS],
RTProcess USING [StartWatchingFaults, StopWatchingFaults, ClearFaultHistory,
EnumerateFaultingProcesses, EnumerateCedarProcesses, CellsAllocated,
PSBIToHandle, GetPSBIPageFaults, Handle, HandleToPSBI, InvalidProcess],
RTRefCounts USING [SetAlwaysTrim],
RTStorageOps USING [DisableReferenceCounting], -- <Cedar>Runtime>
SafeStorage USING [--SetCollectionInterval, --WaitForCollectorDone,
WaitForCollectorStart, ReclamationReason, --SetMaxDataQuanta,--
ReclaimCollectibleObjects],
Timer USING [Seconds, Create, Read, Handle],
ViewerMenus USING [Close, Destroy, Grow],
ViewerOps USING [CreateViewer, SetOpenHeight],
ViewerClasses USING [Viewer],
ViewerTools USING[MakeNewTextViewer, GetSelectionContents];

Snoop: PROGRAM

IMPORTS Buttons, Convert, Inline, Labels, Menus, Plotter, Process,
Real, RealEvent, RealVec, Rope, --RTBases, --RTProcess,
RTRefCounts, RTStorageOps, SafeStorage, Timer,
ViewerMenus, ViewerOps, ViewerTools

= BEGIN OPEN ViewerClasses;

Int: TYPE = LONG INTEGER;

entryHeight: NAT = 15;
entryVSpace: NAT = 2;

heightSoFar: NAT ← entryVSpace;

container: Viewer;

BuildContainer: PROC = BEGIN
container ← ViewerOps.CreateViewer[flavor: $Container, info: [name: "Snoop",
iconic: TRUE, column: right]];
container.menu ← watcherMenu;
END;

----------------------------
collectorButton: Buttons.Button; -- the "Apply" button
tAndSButton: Buttons.Button; -- the "TAndS" button
collectorArg: Viewer; -- the text argument
trimArg: Viewer; -- the text argument
disableRCArg: Viewer; -- the text argument
dataQuantaArg: Viewer; -- the text argument
dqisArg: Viewer; -- the text argument
psbiListArg: Viewer;-- the text argument


PlotPSBIFaultsPerSec: Buttons.ButtonProc = TRUSTED BEGIN
selectionVal: Int ← 0;
selectionVal ← Convert.IntFromRope[ViewerTools.GetSelectionContents[] ! ANY => CONTINUE];
IF selectionVal # 0 THEN PlotPSBIFPS[LOOPHOLE[Inline.LowHalf[selectionVal], PSB.PsbIndex]];
END;

PlotPSBICellsPerSec: Buttons.ButtonProc = TRUSTED BEGIN
selectionVal: Int ← 0;
selectionVal ← Convert.IntFromRope[ViewerTools.GetSelectionContents[] ! ANY => CONTINUE];
IF selectionVal # 0 THEN PlotPSBICPS[LOOPHOLE[Inline.LowHalf[selectionVal], PSB.PsbIndex]];
END;

SetCollectorStuff: Buttons.ButtonProc = TRUSTED BEGIN
-- newInt: Int ← 16384;
trueRope: Rope.ROPE = "TRUE\n";
falseRope: Rope.ROPE = "FALSE\n";
-- argRope: Rope.ROPE ← NARROW[collectorArg.class.get[collectorArg]];
trimArgRope: Rope.ROPENARROW[trimArg.class.get[trimArg]];
disableRCArgRope: Rope.ROPENARROW[disableRCArg.class.get[disableRCArg]];
-- dataQuantaArgRope: Rope.ROPE ← NARROW[dataQuantaArg.class.get[dataQuantaArg]];

-- newInt ← Convert.IntFromRope[argRope ! ANY => CONTINUE];
-- newInt ← MAX[newInt, 1000];
-- newInt ← MIN[newInt, 1000000];
-- argRope ← Convert.ValueToRope[[signed[newInt, 10]]];
-- collectorArg.class.set[collectorArg, argRope, ~container.iconic];
-- [] ← SafeStorage.SetCollectionInterval[newInt];

IF Rope.Fetch[trimArgRope, 0] = 'T OR Rope.Fetch[trimArgRope, 0] = 't
THEN {trimArg.class.set[trimArg, trueRope, ~container.iconic]; RTRefCounts.SetAlwaysTrim[TRUE]}
ELSE IF Rope.Fetch[trimArgRope, 0] = 'F OR Rope.Fetch[trimArgRope, 0] = 'f
THEN {trimArg.class.set[trimArg, falseRope, ~container.iconic]; RTRefCounts.SetAlwaysTrim[FALSE]};

IF Rope.Fetch[disableRCArgRope, 0] = 'T OR Rope.Fetch[trimArgRope, 0] = 't
THEN {disableRCArg.class.set[disableRCArg, trueRope, ~container.iconic];
RTStorageOps.DisableReferenceCounting[]}
ELSE IF Rope.Fetch[disableRCArgRope, 0] = 'F OR Rope.Fetch[trimArgRope, 0] = 'f
THEN {disableRCArg.class.set[disableRCArg, falseRope, ~container.iconic]};

-- newInt ← Convert.IntFromRope[dataQuantaArgRope ! ANY => CONTINUE];
-- dataQuantaArgRope ← Convert.ValueToRope[[signed[newInt, 10]]];
-- dataQuantaArg.class.set[dataQuantaArg, dataQuantaArgRope, ~container.iconic];
-- [] ← SafeStorage.SetMaxDataQuanta[Inline.LowHalf[newInt]];

Process.Pause[Process.MsecToTicks[1000]]; -- aesthetics; make button stay on for a bit
END;

BuildEditableLabel: PROC[y: NAT, name, startVal: Rope.ROPE, container: Viewer, w: NAT ← 0, h: NAT ← 0]
RETURNS[v: Viewer, newY: NAT] =
{fudge: NAT = 1;
l: Labels.Label ← Labels.Create[info: [name: name, parent: container, wx: 0, wy: y, ww: 0, wh: entryHeight, border: FALSE], paint: FALSE];
IF w = 0 THEN w ← 100;
IF h = 0 THEN h ← entryHeight;
v ← ViewerTools.MakeNewTextViewer[[parent: container,
wx: l.wx+l.ww,
wy: y+fudge,
ww: w,
wh: h,
border: FALSE,
data: startVal,
scrollable: FALSE]];
newY ← y + h + 2*entryVSpace};

BuildUneditableLabel: PROC[y: NAT, name, startVal: Rope.ROPE, container: Viewer, w: NAT ← 0, h: NAT ← 0]
RETURNS[v: Viewer, newY: NAT] =
{l: Labels.Label ← Labels.Create[info: [name: name,
parent: container,
wx: 0,
wy: y,
ww: 0,
border: FALSE,
wh: entryHeight]];
IF w = 0 THEN w ← 100;
IF h = 0 THEN h ← entryHeight;
v ← Labels.Create[info: [name: startVal,
parent: container,
wx: l.wx+l.ww,
border: FALSE,
wy: y,
ww: w,
wh: h],
paint: FALSE];
newY ← y + h};

TAndS: Buttons.ButtonProc = TRUSTED
{SafeStorage.ReclaimCollectibleObjects[suspendMe: FALSE,
traceAndSweep: TRUE]};

BuildCollectorIntervalEntry: PROC = BEGIN

collectorButton ← Buttons.Create[info: [name: "Apply",
wx: 300,
wy: heightSoFar,
parent: container],
proc: SetCollectorStuff];
-- tAndSButton ← Buttons.Create[info: [name: "TAndS",
-- wx: 350,
-- wy: heightSoFar,
-- parent: container],
-- proc: TAndS];
-- [collectorArg, heightSoFar] ← BuildEditableLabel
-- [y: heightSoFar,
-- name: "GC Collection Interval: ",
-- startVal: "16384",
-- container: container];
[trimArg, heightSoFar] ← BuildEditableLabel
[y: heightSoFar,
name: "TrimZones: ",
startVal: "FALSE
",
container: container];
[disableRCArg, heightSoFar] ← BuildEditableLabel
[y: heightSoFar,
name: "DisableRC: ",
startVal: "FALSE
",
container: container];
-- [dataQuantaArg, heightSoFar] ← BuildEditableLabel
-- [y: heightSoFar,
-- name: "Max data quanta: ",
-- startVal: "0",
-- container: container];
-- [dqisArg, heightSoFar] ← BuildUneditableLabel
-- [y: heightSoFar,
-- name: "Data quanta in service: ",
-- startVal: "0",
-- container: container];
[] ← Buttons.Create[info: [name: "PlotFaults",
wx: 300,
wy: heightSoFar,
parent: container],
proc: PlotPSBIFaultsPerSec];
heightSoFar ← heightSoFar + 2*entryHeight;

[] ← Buttons.Create[info: [name: "PlotCells",
wx: 300,
wy: heightSoFar,
parent: container],
proc: PlotPSBICellsPerSec];
heightSoFar ← heightSoFar + entryHeight;

[psbiListArg, heightSoFar] ← BuildEditableLabel
[y: heightSoFar,
name: "psbi's: ",
startVal: "0",
container: container,
w: 200,
h: 80];

END;
----------------------------


----------------------------

udtpsq: BOOLEANFALSE; -- UpdateThingsPerSecond process has quit
cwq: BOOLEANFALSE; -- CollectorWatcher process has quit

pause: NAT ← 5; -- seconds between updates
UpdateThingsPerSecond: PROC = BEGIN
UNTIL quit DO
Process.Pause[Process.SecondsToTicks[pause]];
-- Labels.Set[dqisArg,
-- Convert.ValueToRope[[unsigned[RTBases.GetDQIS[], 10]]],
-- ~container.iconic];
UpdateAllocatingPSBIList[];
-- UpdateFaultingPSBIList[];
ENDLOOP;
udtpsq ← TRUE;
END;


samplingInterval: Timer.Seconds = 5;
nEvents: NAT = 100;

UpdateAllocatingPSBIList: PROC = BEGIN

h: RealVec.Handle; -- vector of cell counts, parallel to entry in psbiSeq
index: NAT ← 0; -- index of next allocating process, also the number of them at end of enumeration
PSBISEQ: TYPE = RECORD[elements: SEQUENCE maxLength: CARDINAL OF PSB.PsbIndex];
psbiSeq: REF PSBISEQ;
newAllocatingProcesses: LIST OF ProcessAllocationDesc ← NIL;
allPSBIsChanged: BOOLEANFALSE;

LookAtAllocatingProcess: PROC[ph: RTProcess.Handle]
RETURNS[stop: BOOLEANFALSE] =
{found: BOOLEANFALSE;
l: LIST OF ProcessAllocationDesc ← NIL;
prevL: LIST OF ProcessAllocationDesc ← NIL;
psbi: PSB.PsbIndex = RTProcess.HandleToPSBI[ph ! RTProcess.InvalidProcess => GOTO forgetIt];
cellCount: LONG INTEGER = RTProcess.CellsAllocated[ph ! RTProcess.InvalidProcess => GOTO forgetIt];
numberEnumerated: LONG INTEGER = 100--NOTE--;

IF index = 0 -- this is the first allocating process
THEN {n: NAT = Inline.LowHalf[numberEnumerated];
h ← RealVec.All[n, 0.0];
psbiSeq ← NEW[PSBISEQ[n]];
FOR i: CARDINAL IN [0..n) DO psbiSeq.elements[i] ← PSB.PsbNull; ENDLOOP};

h.elements[index] ← cellCount;
psbiSeq.elements[index] ← psbi;

l ← allocatingProcesses;
UNTIL l = NIL
DO next: LIST OF ProcessAllocationDesc ← l.rest;
IF l.first.psbi = psbi -- found an old ProcessAllocationDesc for this process
THEN {IF prevL = NIL THEN allocatingProcesses ← next ELSE prevL.rest ← next; -- remove its list element from allocatingProcesses
IF cellCount >= l.first.prevCellCount --if not, process has been recirculated
THEN {l.first.prevCellCount ← cellCount;
l.rest ← newAllocatingProcesses;
newAllocatingProcesses ← l;
l ← next;
found ← TRUE};
EXIT}
ELSE {prevL ← l; l ← next};
ENDLOOP;
IF NOT found
THEN newAllocatingProcesses ← CONS[[psbi: psbi, prevCellCount: cellCount],
newAllocatingProcesses];
index ← index + 1;
EXITS forgetIt => RETURN}; --END of LookAtAllocatingProcess

[] ← RTProcess.EnumerateCedarProcesses[LookAtAllocatingProcess];
allocatingProcesses ← newAllocatingProcesses;

IF h = NIL -- there are no allocating processes
THEN {IF allPSBIs # NIL THEN {allPSBIsChanged ← TRUE; allPSBIs ← NIL; allPSBIsLn ← 0}}
ELSE {perms: RealVec.Handle = RealVec.SortOrder[h]; -- small to big
IF index # allPSBIsLn OR NOT RealVec.Equal[perms, prevCellCountPerms]
THEN -- either there are new allocating processes or their sort order has changed
{j: INTEGER ← perms.length-1; -- index-1;
allPSBIsChanged ← TRUE;
allPSBIsLn ← index;
prevCellCountPerms ← perms;

UNTIL j < 0 OR h.elements[Real.FixI[perms[j]]] # 0
DO j ← j - 1 ENDLOOP;
IF j < 0
THEN allPSBIs ← NIL
ELSE
{allPSBIs ← Convert.ValueToRope
[[unsigned[psbiSeq.elements[Real.FixI[perms[j]]], 10]]];
FOR i: NAT DECREASING IN [0..j)
DO psbi: PSB.PsbIndex = psbiSeq.elements[Real.FixI[perms[i]]];
IF h.elements[Real.FixI[perms[i]]] # 0 THEN
allPSBIs
← Rope.Concat
[allPSBIs,
Rope.Concat[", ", Convert.ValueToRope[[unsigned[psbi, 10]]]]];
ENDLOOP
}
}
};
IF allPSBIsChanged THEN Labels.Set[psbiListArg,
IF allPSBIs = NIL THEN "0" ELSE allPSBIs, -- ARRGGH
~container.iconic]
END; -- of UpdateAllocatingPSBIList

UpdateFaultingPSBIList: PROC = BEGIN

h: RealVec.Handle; -- vector of page fault counts, parallel to entry in psbiSeq
index: NAT ← 0; -- index of next faulting process, also the number of them at end of enumeration
PSBISEQ: TYPE = RECORD[elements: SEQUENCE maxLength: CARDINAL OF PSB.PsbIndex];
psbiSeq: REF PSBISEQ;
newFaultingProcesses: LIST OF ProcessFaultDesc ← NIL;
allPSBIsChanged: BOOLEANFALSE;

LookAtFaultingProcess: PROC[psbi: PSB.PsbIndex,
pageFaultCount: LONG INTEGER,
numberEnumerated: LONG INTEGER]
RETURNS[stop: BOOLEANFALSE] =
{found: BOOLEANFALSE;
l: LIST OF ProcessFaultDesc ← NIL;
prevL: LIST OF ProcessFaultDesc ← NIL;

IF index = 0 -- this is the first faulting process
THEN {n: NAT = Inline.LowHalf[numberEnumerated];
h ← RealVec.All[n, 0.0];
psbiSeq ← NEW[PSBISEQ[n]];
FOR i: CARDINAL IN [0..n) DO psbiSeq.elements[i] ← PSB.PsbNull; ENDLOOP};

h.elements[index] ← pageFaultCount;
psbiSeq.elements[index] ← psbi;

l ← faultingProcesses;
UNTIL l = NIL
DO next: LIST OF ProcessFaultDesc ← l.rest;
IF l.first.psbi = psbi -- found an old ProcessFaultDesc for this process
THEN {IF prevL = NIL THEN faultingProcesses ← next ELSE prevL.rest ← next; -- remove its list element from faultingProcesses
IF pageFaultCount >= l.first.prevFaultCount --if not, process has been recirculated
THEN {l.first.prevFaultCount ← pageFaultCount;
l.rest ← newFaultingProcesses;
newFaultingProcesses ← l;
l ← next;
found ← TRUE};
EXIT}
ELSE {prevL ← l; l ← next};
ENDLOOP;
IF NOT found
THEN newFaultingProcesses ← CONS[[psbi: psbi, prevFaultCount: pageFaultCount],
newFaultingProcesses];
index ← index + 1}; --END of LookAtFaultingProcess

[] ← RTProcess.EnumerateFaultingProcesses[LookAtFaultingProcess];
faultingProcesses ← newFaultingProcesses;

IF h = NIL -- there are no faulting processes
THEN {IF allPSBIs # NIL THEN {allPSBIsChanged ← TRUE; allPSBIs ← NIL; allPSBIsLn ← 0}}
ELSE {perms: RealVec.Handle = RealVec.SortOrder[h]; -- small to big
IF index # allPSBIsLn OR NOT RealVec.Equal[perms, prevFaultCountPerms]
THEN -- either there are new faulting processes or their sort order has changed
{j: INTEGER ← perms.length-1; -- index-1;
allPSBIsChanged ← TRUE;
allPSBIsLn ← index;
prevFaultCountPerms ← perms;

UNTIL j < 0 OR h.elements[Real.FixI[perms[j]]] # 0
DO j ← j - 1 ENDLOOP;
IF j < 0
THEN allPSBIs ← NIL
ELSE
{allPSBIs ← Convert.ValueToRope
[[unsigned[psbiSeq.elements[Real.FixI[perms[j]]], 10]]];
FOR i: NAT DECREASING IN [0..j)
DO psbi: PSB.PsbIndex = psbiSeq.elements[Real.FixI[perms[i]]];
IF h.elements[Real.FixI[perms[i]]] # 0 THEN
allPSBIs
← Rope.Concat
[allPSBIs,
Rope.Concat[", ", Convert.ValueToRope[[unsigned[psbi, 10]]]]];
ENDLOOP
}
}
};
IF allPSBIsChanged THEN Labels.Set[psbiListArg,
IF allPSBIs = NIL THEN "0" ELSE allPSBIs, -- ARRGGH
~container.iconic]
END; -- of UpdateFaultingPSBIList

allPSBIs: Rope.ROPENIL;
allPSBIsLn: NAT ← 0;

prevFaultCountPerms: RealVec.Handle ← NIL;
faultingProcesses: LIST OF ProcessFaultDesc ← NIL;
ProcessFaultDesc: TYPE = RECORD[psbi: PSB.PsbIndex,
prevFaultCount: REAL];

prevCellCountPerms: RealVec.Handle ← NIL;
allocatingProcesses: LIST OF ProcessAllocationDesc ← NIL;
ProcessAllocationDesc: TYPE = RECORD[psbi: PSB.PsbIndex,
prevCellCount: REAL];


PSBIFaultsPerSecSampler: PROC[s: REF ANY] RETURNS[REAL] =
{psbi: PSB.PsbIndex = NARROW[s, REF PSB.PsbIndex]^;
RETURN[RTProcess.GetPSBIPageFaults[psbi]];
};

PlotPSBIFPS: PROC[psbi: PSB.PsbIndex] =
{label: Rope.ROPE = Rope.Concat
[Rope.Concat["PFaults",
Convert.ValueToRope[[unsigned[psbi, 10]]]
],
", faults/sec"];
[] ← Plotter.Create[label: label,
eventSource: RealEvent.CreateTimerDrivenStream
[interval: pause,
sampler: PSBIFaultsPerSecSampler,
stateInfo: NEW[PSB.PsbIndex ← psbi]],
autoRepaint: TRUE,
nEvents: nEvents,
plotValueDifferences: TRUE,
plotValuePerSecond: TRUE]
};

PSBICellsPerSecSampler: PROC[s: REF ANY] RETURNS[REAL] =
{psbi: PSB.PsbIndex = NARROW[s, REF PSB.PsbIndex]^;
RETURN[RTProcess.CellsAllocated[RTProcess.PSBIToHandle[psbi]]];
};

PlotPSBICPS: PROC[psbi: PSB.PsbIndex] =
{label: Rope.ROPE = Rope.Concat
[Rope.Concat["PCells",
Convert.ValueToRope[[unsigned[psbi, 10]]]
],
", cells/sec"];
[] ← Plotter.Create[label: label,
eventSource: RealEvent.CreateTimerDrivenStream
[interval: pause,
sampler: PSBICellsPerSecSampler,
stateInfo: NEW[PSB.PsbIndex ← psbi]],
autoRepaint: TRUE,
nEvents: nEvents,
plotValueDifferences: TRUE,
plotValuePerSecond: TRUE]
};


----------------------------



----------------------------
collectorStatus: Labels.Label;

CollectorWatcher: PROC = BEGIN
-- active: Rope.ROPE = "ACTIVE: ";
-- inactive: Rope.ROPE = "inactive. Prev: ";
timer: Timer.Handle = Timer.Create[];
es: RealEvent.StreamHandle = RealEvent.CreateEventDrivenStream[];
[] ← Plotter.Create[label: "collectorWordsReclaimedHistory",
eventSource: es,
autoRepaint: TRUE,
nEvents: nEvents,
connectivity: vertical,
pointShape: filledBox
];
UNTIL quit DO
startIncarnation, endIncarnation: CARDINAL;
-- cReasons: ARRAY SafeStorage.ReclamationReason OF Rope.ROPE =
-- ["clientRC", "clientTAndS", "clientNoTrace", "rcTOverflow",
-- "allocInterval", "quantaNeeded", "finalizationThreshold"];
r: SafeStorage.ReclamationReason;
objsAlloc: Int;
objsReclaimed, wReclaimed: Int;
[reason: r,
objectsAllocated: objsAlloc,
incarnation: startIncarnation] ← SafeStorage.WaitForCollectorStart[];
IF quit THEN EXIT;

es.procs.put[es, NEW[RealEvent.Object ← [sampleValue: 100.0, time: timer.Read[].time]]];

-- Labels.Set[collectorStatus,
-- Rope.Concat[
-- Rope.Concat[Rope.Concat[active, cReasons[r]],
-- ", allocObjs: "],
-- Convert.ValueToRope[[unsigned[objsAlloc, 10]]]]];
[reason: r,
objectsReclaimed: objsReclaimed,
wordsReclaimed: wReclaimed,
incarnation: endIncarnation] ← SafeStorage.WaitForCollectorDone[];

es.procs.put[es, NEW[RealEvent.Object ← [sampleValue: wReclaimed, time: timer.Read[].time]]];

IF quit THEN EXIT;
-- Labels.Set[collectorStatus,
-- Rope.Concat[
-- Rope.Concat[Rope.Concat[inactive, cReasons[r]],
-- ", reclaimedObjs: "],
-- Convert.ValueToRope[[unsigned[objsReclaimed, 10]]]]];
ENDLOOP;
cwq ← TRUE;
END;

BuildCollectorWatcherEntry: PROC = BEGIN
collectorStatusLabel: Labels.Label;
fudge: NAT = 1;
startVal: Rope.ROPE ← "16384";
collectorStatusLabel ← Labels.Create[info: [name: "GC: ", parent: container, wx: 0, wy: heightSoFar, ww: 0, wh: entryHeight]];
collectorStatus ← Labels.Create[info: [name: "inactive. ",
parent: container,
wx: collectorStatusLabel.wx+collectorStatusLabel.ww,
wy: heightSoFar,
ww: 0, wh: entryHeight]];
heightSoFar ← heightSoFar + entryHeight + entryVSpace + entryVSpace;
END;
----------------------------


----------------------------
quit: BOOLEANFALSE; -- watched by various process to quit

watcherMenu: Menus.Menu ← Menus.CreateMenu[];

BuildWatcherMenu: PROC = BEGIN
Menus.InsertMenuEntry[watcherMenu, Menus.CreateEntry["Destroy", MyDestroy]];
Menus.InsertMenuEntry[watcherMenu, Menus.CreateEntry["Grow", ViewerMenus.Grow]];
Menus.InsertMenuEntry[watcherMenu, Menus.CreateEntry["Close", ViewerMenus.Close]];
END;

MyDestroy: Menus.MenuProc = TRUSTED BEGIN
IF quit THEN RETURN ELSE quit ← TRUE;
Process.Detach[FORK DoMyDestroy[parent: parent, clientData: clientData]];
END;

DoMyDestroy: Menus.MenuProc = TRUSTED BEGIN
RTProcess.StopWatchingFaults[];
UNTIL cwq AND udtpsq DO Process.Pause[Process.SecondsToTicks[1]] ENDLOOP;
-- let extant processes finish up
ViewerMenus.Destroy[parent: parent, clientData: clientData];
watcherMenu ← NIL;
collectorStatus ← NIL;
prevFaultCountPerms ← NIL;
faultingProcesses ← NIL;
allPSBIs ← NIL;
container ← NIL;
collectorButton ← NIL;
tAndSButton ← NIL;
collectorArg ← NIL;
trimArg ← NIL;
disableRCArg ← NIL;
dataQuantaArg ← NIL;
dqisArg ← NIL;
psbiListArg ← NIL;
END;
----------------------------



-- START HERE

BuildWatcherMenu[];

BuildContainer[]; -- build enclosing viewer

--BuildCollectorWatcherEntry[];

Process.Detach[FORK CollectorWatcher];

BuildCollectorIntervalEntry[];

RTProcess.StartWatchingFaults[];
RTProcess.ClearFaultHistory[];

Process.Detach[FORK UpdateThingsPerSecond[]];

ViewerOps.SetOpenHeight[container, heightSoFar];

END.