-- 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], -- 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.ROPE _ NARROW[trimArg.class.get[trimArg]]; disableRCArgRope: Rope.ROPE _ NARROW[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: BOOLEAN _ FALSE; -- UpdateThingsPerSecond process has quit cwq: BOOLEAN _ FALSE; -- 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: BOOLEAN _ FALSE; LookAtAllocatingProcess: PROC[ph: RTProcess.Handle] RETURNS[stop: BOOLEAN _ FALSE] = {found: BOOLEAN _ FALSE; 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: BOOLEAN _ FALSE; LookAtFaultingProcess: PROC[psbi: PSB.PsbIndex, pageFaultCount: LONG INTEGER, numberEnumerated: LONG INTEGER] RETURNS[stop: BOOLEAN _ FALSE] = {found: BOOLEAN _ FALSE; 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.ROPE _ NIL; 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: BOOLEAN _ FALSE; -- 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.