-- Watch.mesa; (Written by McGregor on 13-Oct-81 15:59:40)
-- Edited by Paul Rovner on 14-Oct-81 9:34:11)
-- Edited by Russ Atkinson on 22-Jan-82 10:43:27
-- Edited by McGregor on July 26, 1982 11:34 am
-- Edited by MBrown on July 16, 1982 2:08 pm

DIRECTORY
Buttons: TYPE USING [Button, ButtonProc, Create, SetDisplayStyle],
CIFSFeedback: TYPE USING [Register],
Convert: TYPE USING [IntFromRope, ValueToRope],
Graphics: TYPE USING [black, DrawBox, SetColor, SetStipple, white],
Labels: TYPE USING [Create, Label, Set],
Menus: TYPE USING
[CreateMenu, InsertMenuEntry, Menu, MenuProc],
PrincOpsRuntime,
Process: TYPE USING [Detach, MsecToTicks, Pause, SecondsToTicks, SetPriority, Yield],
Real USING [LargestNumber],
Rope: TYPE USING [Cat, Concat, Length, Replace, ROPE, SkipTo],
RTProcess: TYPE USING [GetTotalPageFaults, StartWatchingFaults],
SafeStorage: TYPE USING
[NWordsAllocated, SetCollectionInterval, WaitForCollectorDone,
WaitForCollectorStart, ReclaimCollectibleObjects, ReclamationReason],
SDDefs USING [SD, sGFTLength],
ShowTime: TYPE USING [GetMark, Microseconds],
Space,
VFonts USING [StringWidth],
ViewerMenus: TYPE USING [Close, Grow, Move],
ViewerOps: TYPE USING
[CreateViewer, DestroyViewer, RegisterViewerClass, PaintViewer, SetOpenHeight],
ViewerClasses: TYPE,
ViewerSpecs: TYPE,
ViewerTools: TYPE USING [MakeNewTextViewer],
Volume: TYPE USING [PageCount, systemID, GetAttributes];

Watch: MONITOR

IMPORTS Buttons, CIFSFeedback, Convert, Graphics, Labels, Menus, Process, RTProcess,
Rope, SafeStorage, ShowTime, Space, VFonts, ViewerMenus, ViewerOps,
ViewerSpecs, ViewerTools, Volume

= BEGIN OPEN Process, Rope, ViewerClasses, ViewerSpecs;

entryHeight: CARDINAL = 15;
heightSoFar: CARDINAL ← 0;

container: ViewerClasses.Viewer ← NIL;

---- ---- ---- ---- ---- ---- ----
collectorArg: ViewerClasses.Viewer ← NIL; -- the text argument
freeMdsGfi: ViewerClasses.Viewer ← NIL;
totalFaultsLabel: ViewerClasses.Viewer ← NIL;
cifsStatusLabel: ViewerClasses.Viewer ← NIL;

mdsPages: CARDINAL ← 0;
remGFI: CARDINAL ← 0;
diskPages: Volume.PageCount ← 0;

Decimal: PROC [int: INT] RETURNS [ROPE] = {
RETURN[Convert.ValueToRope[[signed[int, 10]]]]};

SetCollectorInterval: Buttons.ButtonProc = {
newInt: LONG INTEGER ← 16384;
argRope: ROPENARROW[collectorArg.class.get[collectorArg]];
newInt ←
Convert.IntFromRope
[argRope
! ANY => CONTINUE];
newInt ← MAX[newInt, 1000];
newInt ← MIN[newInt, 1000000];
argRope ← Decimal[newInt];
collectorArg.class.set[collectorArg, argRope, NOT container.iconic];
[] ← SafeStorage.SetCollectionInterval[newInt];
};

BuildCollectorIntervalEntry: PROC = {
startVal: ROPE ← "16384";
cifsLabel: Labels.Label;
faultsLabel: Labels.Label ←
Labels.Create
[info: [name: "faults ?????? ",
wy: heightSoFar,
wh: entryHeight,
parent: container,
border: FALSE],
paint: FALSE];
mdsLabel: Labels.Label ←
Labels.Create
[info: [name: "mds ??? gfi ??? disk ????? ",
wx: faultsLabel.wx + faultsLabel.ww,
wy: heightSoFar,
wh: entryHeight,
parent: container,
border: FALSE],
paint: FALSE];
collectorButton: Buttons.Button ←
Buttons.Create
[info: [name: "GC interval ← ",
wx: mdsLabel.wx + mdsLabel.ww,
wy: heightSoFar,
wh: entryHeight,
parent: container,
border: FALSE],
fork: TRUE,
proc: SetCollectorInterval,
paint: FALSE];
collectorArg ←
ViewerTools.MakeNewTextViewer
[info: [parent: container,
wx: collectorButton.wx + collectorButton.ww,
wy: heightSoFar+2,
ww: 100,
wh: entryHeight,
data: startVal,
scrollable: FALSE,
border: FALSE]];
heightSoFar ← collectorButton.wy + collectorButton.wh;
freeMdsGfi ← mdsLabel;
totalFaultsLabel ← faultsLabel;
cifsLabel ←
Labels.Create
[info: [name: "",
wy: heightSoFar,
ww: VFonts.StringWidth["0"]*67,
wh: entryHeight,
parent: container,
border: FALSE],
paint: FALSE];
heightSoFar ← heightSoFar + cifsLabel.wh;
cifsStatusLabel ← cifsLabel;
};
---- ---- ---- ---- ---- ---- ----


---- ---- ---- ---- ---- ---- ----
mark: ShowTime.Microseconds ← 0;
words, ticks: LONG CARDINAL ← 0;
maxTicks: REAL ← 0;
minTicks: REAL ← Real.LargestNumber;


PaintLabel: PROC = {ViewerOps.PaintViewer[cifsStatusLabel, all]};
UpdateCifsStatus: PROC [r: ROPE] = BEGIN
-- the paint process is forked in case CIFS calls out from init'ing a Tioga viewer.
IF r.Length[] = 0 THEN Labels.Set[cifsStatusLabel, "CIFS is idle", FALSE]
ELSE Labels.Set[cifsStatusLabel, r, FALSE];
Process.Detach[FORK PaintLabel];
END;

SampleProcess: PROC = BEGIN
Increment: ENTRY PROC = INLINE {ticks ← ticks + 1};
-- just sits there counting as fast as it can
 Process.SetPriority[LOOPHOLE[0]]; -- run only when nothing else wants to
UNTIL quit DO Process.Yield[]; Increment[]; ENDLOOP;   -- forever
END;

UpdateThingsPerSecond: PROC = {
nextWords: LONG CARDINAL;
nextMark: ShowTime.Microseconds;
faults, numFaults: LONG CARDINAL ← 0;
deltaTime, deltaWords, deltaFaults, realTicks: REAL;
pause: CARDINAL ← 5; -- seconds between updates
Inner: ENTRY PROC = {
-- this proc determines words/sec, faults/sec for the graphs
-- and the remaining mds pages and gfi slots
msg: ROPENIL;
insertNum: PROC [num: INT] = {
pos: INT ← msg.SkipTo[0, "?"];
msg ← msg.Replace[pos, 1, Convert.ValueToRope[[signed[num]]]]};
IF quit THEN RETURN;
nextMark ← ShowTime.GetMark[];
deltaTime ← (nextMark - mark)*1.0E-6;
nextWords ← SafeStorage.NWordsAllocated[];
deltaWords ← nextWords - words;
numFaults ← RTProcess.GetTotalPageFaults[];
deltaFaults ← numFaults - faults;
realTicks ← ticks/deltaTime;
maxTicks ← MAX[realTicks, maxTicks];
minTicks ← MIN[realTicks, minTicks];
GraphSet[gcCounter, deltaWords/deltaTime,
IF maxTicks=minTicks THEN 0 ELSE 1-(realTicks-minTicks)/(maxTicks-minTicks),
 deltaFaults/deltaTime];
ticks ← 0;
{newMdsPages: CARDINAL ← 0;
newRemGFI: CARDINAL ← 0;
newDiskPages: Volume.PageCount ← Volume.GetAttributes[Volume.systemID].freePageCount;
firstMdsPage: CARDINAL ← Space.VMPageNumber[Space.mds];
FOR page: CARDINAL IN [firstMdsPage..firstMdsPage + 256) DO
IF Space.GetHandle[page] = Space.mds THEN
newMdsPages ← newMdsPages + 1;
ENDLOOP;
FOR i: CARDINAL DECREASING IN [1..SDDefs.SD[SDDefs.sGFTLength]) DO
item: PrincOpsRuntime.GFTItem ← PrincOpsRuntime.GFT[i];
IF item.data # 0 THEN EXIT;
newRemGFI ← newRemGFI + 1;
ENDLOOP;
IF newMdsPages # mdsPages OR newRemGFI # remGFI OR
newDiskPages # diskPages THEN
{msg ← "mds ? gfi ? disk ? ";
insertNum[mdsPages ← newMdsPages];
insertNum[remGFI ← newRemGFI];
insertNum[diskPages ← newDiskPages];
Labels.Set[freeMdsGfi, msg, TRUE];
};
IF numFaults # faults THEN
{msg ← "faults ?";
insertNum[LOOPHOLE[(faults ← numFaults), INT]];
Labels.Set[totalFaultsLabel, msg, TRUE];
};
};
words ← nextWords;
mark ← nextMark;
faults ← numFaults;
};
labelSep: CARDINAL = 14;
W2: PROC [r: ROPE] RETURNS [INTEGER] = INLINE {RETURN[VFonts.StringWidth[r]/2]};
gcLabel: Labels.Label ← Labels.Create
[info: [name: "Words/Sec ", parent: container, wy: heightSoFar,
 wh: entryHeight, border: FALSE], paint: FALSE];
lLabel: Labels.Label ← Labels.Create
[info: [name: "CPU Load ", parent: container, wy: heightSoFar+gcLabel.wh-1,
 wh: entryHeight, border: FALSE], paint: FALSE];
fLabel: Labels.Label ← Labels.Create
[info: [name: "Faults/Sec ", parent: container, wy: heightSoFar+labelSep+entryHeight,
wh: entryHeight, border: FALSE], paint: FALSE];
xTemp: CARDINALMAX[gcLabel.ww, fLabel.ww] + 10;
gcCounter: ViewerClasses.Viewer ← ViewerOps.CreateViewer
[flavor: $BarGraph, info: [parent: container, wx: xTemp,
wy: heightSoFar + labelSep, ww: openRightWidth - xTemp - 5,
wh: 17, data: NEW[GraphDataRec ← [[fullScale: 5], [fullScale: -1], [fullScale: 2]]]],
paint: FALSE];
xTemp ← gcCounter.ww/5;
[] ← Labels.Create
[info: [name: "1", parent: container, wx: gcCounter.wx - W2["1"], wy: heightSoFar,
wh: entryHeight, border: FALSE], paint: FALSE];
[] ← Labels.Create
[info: [name: "10", parent: container, wx: gcCounter.wx + xTemp - W2["10"],
wy: heightSoFar, wh: entryHeight, border: FALSE], paint: FALSE];
[] ← Labels.Create
[info: [name: "100", parent: container, wx: gcCounter.wx + 2*xTemp - W2["100"],
wy: heightSoFar, wh: entryHeight, border: FALSE], paint: FALSE];
[] ← Labels.Create
[info: [name: "1000", parent: container, wx: gcCounter.wx + 3*xTemp - W2["1000"],
wy: heightSoFar, wh: entryHeight, border: FALSE], paint: FALSE];
[] ← Labels.Create
[info: [name: "10000", parent: container, wx: gcCounter.wx + 4*xTemp - W2["10000"],
wy: heightSoFar, wh: entryHeight, border: FALSE], paint: FALSE];
heightSoFar ← heightSoFar + labelSep + entryHeight;
xTemp ← gcCounter.ww/4;
[] ← Labels.Create
[info: [name: "1", parent: container, wx: gcCounter.wx - W2["1"], wy: heightSoFar,
wh: entryHeight, border: FALSE], paint: FALSE];
[] ← Labels.Create
[info: [name: "3", parent: container, wx: gcCounter.wx + xTemp - W2["3"], wy: heightSoFar,
wh: entryHeight, border: FALSE], paint: FALSE];
[] ← Labels.Create
[info: [name: "10", parent: container, wx: gcCounter.wx + xTemp*2 - W2["10"],
wy: heightSoFar, border: FALSE, wh: entryHeight], paint: FALSE];
[] ← Labels.Create
[info: [name: "30", parent: container, wx: gcCounter.wx + xTemp*3 - W2["30"],
wy: heightSoFar, wh: entryHeight, border: FALSE], paint: FALSE];
heightSoFar ← fLabel.wy + fLabel.wh - 1;
words ← SafeStorage.NWordsAllocated[];
mark ← ShowTime.GetMark[];
waitingBuild ← FALSE;
WHILE ~start DO Process.Pause[Process.MsecToTicks[200]]; ENDLOOP;
WHILE NOT quit DO
Pause[SecondsToTicks[pause]];
Inner[];
ENDLOOP
};

---- ---- ---- ---- ---- ---- ----
-- Create a bar-graph viewer class

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];

GraphPaint: ViewerClasses.PaintProc = {
OPEN Graphics;
myGrey: CARDINAL = 122645B;
data: GraphData ← NARROW[self.data];
IF whatChanged = NIL THEN
data.bottom.updateHint ← data.middle.updateHint ← data.top.updateHint ← bigger;
IF data.top.updateHint = bigger
THEN
{SetStipple[context, myGrey];
DrawBox[context, [0, 2*self.ch/3, data.top.value, self.ch]]}
ELSE
IF data.top.updateHint = smaller THEN
{SetColor[context, white];
DrawBox[context, [data.top.value, 2*self.ch/3, self.cw, self.ch]]};
IF data.middle.updateHint = bigger
THEN
{SetStipple[context, myGrey];
DrawBox[context, [0, self.ch/3, data.middle.value, 2*self.ch/3]]}
ELSE
IF data.middle.updateHint = smaller THEN
{SetColor[context, white];
DrawBox[context, [data.middle.value, self.ch/3, self.cw, 2*self.ch/3]]};
IF data.bottom.updateHint = bigger
THEN
{SetStipple[context, myGrey];
DrawBox[context, [0, 0, data.bottom.value, self.ch/3]]}
ELSE
IF data.bottom.updateHint = smaller THEN
{SetColor[context, white];
DrawBox[context, [data.bottom.value, 0, self.cw, self.ch/3]]};
SetColor[context, black]
};

Log10: --Fast-- PROC [x: REAL] RETURNS [lx: REAL] = {
-- truncated for values of [1..inf), 3-4 good digits
-- algorithm from Abramowitz: Handbook of Math Functions, p. 68
sqrt10: REAL = 3.162278;
t: REAL;
lx ← 0;
WHILE x > 10 DO
x ← x/10; lx ← lx + 1
ENDLOOP; -- scale to [1..10]
IF x > sqrt10 THEN {x ← x/sqrt10; lx ← lx + 0.5};
-- scale to [1..1/sqrt10]
-- magic cubic approximation
t ← (x - 1)/(x + 1);
lx ← lx + 0.86304*t + 0.36415*(t*t*t)
};

-- use this routine to set the bar graph to new values
GraphSet: PROC [graph: ViewerClasses.Viewer, t, m, b: REAL] = {
tempR: REAL;
myData: GraphData ← NARROW[graph.data];
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
ViewerOps.PaintViewer[graph, client, FALSE, $Update]
};

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

CauseGCHit: Buttons.ButtonProc = {
Buttons.SetDisplayStyle[viewer, $BlackOnGrey];
SafeStorage.ReclaimCollectibleObjects[TRUE, ~redButton];
Buttons.SetDisplayStyle[viewer, $BlackOnWhite];
};

CollectorWatcher: PROC = {
active: ROPE = "ACTIVE: ";
inactive: ROPE = "inactive.";
collectorStatusLabel: Buttons.Button ←
Buttons.Create
[info: [name: "GC",
wy: heightSoFar,
wh: entryHeight,
parent: container,
border: FALSE],
fork: TRUE,
proc: CauseGCHit,
paint: FALSE];
collectorStatus: Labels.Label ←
Labels.Create
[info: [name: inactive,
parent: container,
wx: collectorStatusLabel.wx + collectorStatusLabel.ww,
wy: heightSoFar,
ww: openRightWidth,
wh: entryHeight,
border: FALSE],
paint: FALSE];
SetCollectorStatus: ENTRY PROC [status: ROPE] = {
IF NOT quit THEN
Labels.Set[collectorStatus, status, NOT container.iconic]
};
heightSoFar ← collectorStatusLabel.wy + collectorStatusLabel.wh;
waitingBuild ← FALSE;
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;
SetCollectorStatus[Rope.Concat[active, cReasons[r]]];
[incarnation: GCnumber, reason: r, wordsReclaimed: wrds, objectsReclaimed: objs] ←
SafeStorage.WaitForCollectorDone[];
IF quit THEN EXIT;
msg ← msg.Cat
[" (GC#", Decimal[GCnumber], " got ",
Decimal[LOOPHOLE[wrds]].Concat[" words, "],
Decimal[LOOPHOLE[objs]].Concat[" objs)"]];
SetCollectorStatus[msg]
ENDLOOP
};

---- ---- ---- ---- ---- ---- ----
quit: BOOLEANFALSE; -- watched by various process to quit
waitingBuild, start: BOOLFALSE; -- change flags for builders (Who did this crock? -SM)

MyDestroy: ENTRY Menus.MenuProc = {
CIFSFeedback.Register[NIL];
quit ← TRUE;
ViewerOps.DestroyViewer[viewer]
};
---- ---- ---- ---- ---- ---- ----

Builder: PROC = {
graphClass: ViewerClasses.ViewerClass ←
NEW[ViewerClasses.ViewerClassRec ← [paint: GraphPaint]];

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

ViewerOps.RegisterViewerClass[$BarGraph, graphClass];
Menus.InsertMenuEntry[watcherMenu, "Destroy", MyDestroy];
Menus.InsertMenuEntry[watcherMenu, "<-->", ViewerMenus.Move];
Menus.InsertMenuEntry[watcherMenu, "Grow", ViewerMenus.Grow];
Menus.InsertMenuEntry[watcherMenu, "Close", ViewerMenus.Close];

-- build enclosing viewer
container ← ViewerOps.CreateViewer[flavor: $Container, info: [name: "Watch", column: right,
 menu: watcherMenu, scrollable: FALSE]];

waitingBuild ← TRUE;
Detach[FORK SampleProcess[]];
Detach[FORK UpdateThingsPerSecond[]];
WHILE waitingBuild DO Process.Pause[Process.MsecToTicks[200]]; ENDLOOP;

waitingBuild ← TRUE;
heightSoFar ← heightSoFar + 1;
Detach[FORK CollectorWatcher];
WHILE waitingBuild DO Process.Pause[Process.MsecToTicks[200]]; ENDLOOP;
BuildCollectorIntervalEntry[];
start ← TRUE;
UpdateCifsStatus[""];

-- set an aesthetically sized open window
ViewerOps.SetOpenHeight[container, heightSoFar + 3];
IF NOT container.iconic THEN ViewerOps.PaintViewer[container, all];
CIFSFeedback.Register[UpdateCifsStatus];

RTProcess.StartWatchingFaults[]};

Process.Detach[FORK Builder];

END.