BuildTool:
PROC = {
BuildTool is a separate procedure to factor out the work done to create a viewer from the work done to refresh the Watch tool. This is especially important to reduce the liklihood of overflowing storage in Pass 5 of the compiler, I think.
graphClass: ViewerClasses.ViewerClass =
NEW[ViewerClasses.ViewerClassRec ← [paint: GraphPaint, destroy: MyDestroy]];
lastX: INTEGER;
nextX: INTEGER ← gapX;
nextY: INTEGER ← 0;
AddLabel:
PROC [prefix:
ROPE, chars:
NAT ← 0, lastInLine:
BOOL ←
FALSE, ww:
INTEGER ← 0]
RETURNS [label: Label] = {
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};
};
AddTriplet:
PROC [lead:
ROPE, ww:
INTEGER]
RETURNS [trip: Triplet] = {
IF lead # NIL THEN [] ← AddLabel[lead, 0, FALSE, ww];
trip ← NEW[TripletRep ← [AddLabel["calls", 6], NIL, NIL]];
Must do this here, since order of evaluation matters
trip.pages ← AddLabel["pages", 7];
trip.msecs ← AddLabel["msecs", 8, TRUE];
};
AddButton:
PROC [buttonName:
ROPE ←
NIL, chars:
NAT ← 0, parm: Parameter ←
NIL, proc: Buttons.ButtonProc ←
NIL, lastInLine:
BOOL ←
FALSE]
RETURNS [button: Buttons.Button] = {
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};
};
SimpleLabel:
PROC [name:
ROPE, wx,wy:
INTEGER] = {
[] ← Labels.Create[info: [name: name, parent: container, wx: wx, wy: wy, border: FALSE], paint: FALSE];
};
CreateGraph:
PROC
RETURNS[viewer: ViewerClasses.Viewer] = {
W2: PROC [r: ROPE] RETURNS [INTEGER] = {RETURN[VFonts.StringWidth[r]/2]};
xTemp: INTEGER;
yTemp: INTEGER;
wordsLabel ← AddLabel["Words", 9, TRUE]; xTemp ← lastX;
decayCpuLabel ← AddLabel["CPU Load", 3, 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;
yTemp ← wordsLabel.wy;
SimpleLabel["1", viewer.wx - W2["1"], yTemp];
SimpleLabel["10", viewer.wx + xTemp - W2["10"], yTemp];
SimpleLabel["100", viewer.wx + 2*xTemp - W2["100"], yTemp];
SimpleLabel["1000", viewer.wx + 3*xTemp - W2["1000"], yTemp];
SimpleLabel["10000", viewer.wx + 4*xTemp - W2["10000"], yTemp];
xTemp ← viewer.ww/4;
yTemp ← diskIOLabel.wy;
SimpleLabel["0%", viewer.wx - W2["1"], yTemp];
SimpleLabel["25%", viewer.wx + xTemp - W2["3"], yTemp];
SimpleLabel["50%", viewer.wx + xTemp*2 - W2["10"], yTemp];
SimpleLabel["75%", viewer.wx + xTemp*3 - W2["30"], yTemp];
}; --CreateGraph--
UserProfile.CallWhenProfileChanges[ProfileChanged];
[] ← IdleExtras.RegisterIdleHandler[NoticeIdleTransition];
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", 6];
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];
TRUSTED {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 6A: Idle minutes
minutesIdleLabel ← AddLabel["Idle minutes", 5];
millisIdleLabel ← AddLabel["millis", 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", 3];
pagesCleanedLabel ← AddLabel["pages", 7];
laundryWakeupsLabel ← AddLabel["wakeups", 8, TRUE];
panicLaundryWakeupsLabel ← AddLabel[" panic", 3];
pagesCleanedPanicLabel ← AddLabel["panicPgs", 4];
laundryCleanCallsLabel ← AddLabel["cleanCalls", 5];
uselessLaundryWakeupsLabel ← AddLabel["useless", 8, 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;
fsOpenTriplet ← AddTriplet[NIL, ww];
fsCreateTriplet ← AddTriplet[" Create", ww];
fsDeleteTriplet ← AddTriplet[" Delete", ww];
fsExtendTriplet ← AddTriplet[" Extend", ww];
fsContractTriplet ← AddTriplet[" Contract", ww];
fsReadTriplet ← AddTriplet[" Read", ww];
fsWriteTriplet ← AddTriplet[" Write", ww];
};
biggerOpenHeight ← nextY + gapY - 1;
initialize measurments
mark ← BasicTime.GetClockPulses[];
watchStats.idleCount ← 0;
words ← SafeStorage.NWordsAllocated[];
TRUSTED {
Process.Detach[FORK IdleProcess[]];
Process.Detach[FORK DisplayFSStatus[readFSlabel, writeFSlabel, flushFSlabel, NreadFSlabel, NwriteFSlabel, NflushFSlabel]];
};
};
WHILE
NOT quit
AND
NOT container.destroyed
DO
reads,writes,readPgs,writePgs: INT;
diskPercent: REAL ← 0.0;
deltaMillis: LONG CARDINAL;
diskIO: INT ← 0;
{
Get the time delta
nextMark: BasicTime.Pulses = BasicTime.GetClockPulses[];
idleTemp:
INT ← watchStats.idleCount;
Make this follow the sampling of GetClockPulses as quickly as possible
delta: LONG CARDINAL ← BasicTime.PulsesToMicroseconds[nextMark - mark];
deltaMillis ← (delta + 500) / 1000;
IF deltaMillis <= 10
THEN {
Not enough precision, so wait for the minimum time, then loop
Process.Pause[1];
LOOP};
mark ← nextMark;
Update the idle rate data
idleRate ← (idleTemp-lastIdle) / deltaMillis;
lastIdle ← idleTemp;
IF idleRate > maxIdleRate
THEN
If enough time has elapsed, then use new (higher) idle rate. If the test interval is too short, then don't trust the new idle rate (error is likely to be too high).
IF deltaMillis > 100 THEN maxIdleRate ← idleRate ELSE idleRate ← maxIdleRate;
};
{
Update the disk numbers
newActiveDiskPulses, newTotalDiskPulses: BasicTime.Pulses;
[active: newActiveDiskPulses, total: newTotalDiskPulses, reads: reads, writes: writes, readPages: readPgs, writePages: writePgs] ← Disk.GetStatistics[];
diskIO ← reads+writes;
IF newTotalDiskPulses # oldTotalDiskPulses
THEN {
IF newActiveDiskPulses # oldActiveDiskPulses
THEN {
diskPercent ← (1.0*(newActiveDiskPulses-oldActiveDiskPulses))
/ (newTotalDiskPulses-oldTotalDiskPulses);
oldActiveDiskPulses ← newActiveDiskPulses;
};
oldTotalDiskPulses ← newTotalDiskPulses;
};
};
{
Update the alloc data
deltaWords: INT ← SafeStorage.NWordsAllocated[] - words;
words ← words + deltaWords;
wordsRate ← (deltaWords * 1000 + 500) / deltaMillis;
};
{
Maintain the exponentially decaying CPU rate.
frac: REAL ← deltaMillis * 1E-4;
IF frac > 1.0 THEN frac ← 1.0;
watchStats.cpuLoad ← 1 - idleRate/(maxIdleRate*1.0);
decayCpuRate ← (1.0-frac)*decayCpuRate + frac*watchStats.cpuLoad;
};
IF decayCpuRate < idleCpuRate
AND diskPercent = 0.0
AND wordsRate = 0
THEN TestForIdle[deltaMillis]
ELSE minutesSpentIdle ← millisSpentIdle ← 0;
millisSinceLastBigBang ← MIN[longPause, millisSinceLastBigBang + deltaMillis];
Test for a valid viewer still existing
IF quit OR container.destroyed THEN GO TO done;
IF container.iconic
OR idleStatus = asleep
THEN {
The user really does not want to see the data now. We performed the above calculations just to keep the data current (and to avoid overflow in some numbers).
Process.Pause[Process.SecondsToTicks[2]];
LOOP;
};
{
Sample the new VM & GFI numbers
pagesAllocated, pagesFreed, pagesInPartition: VM.PageCount;
TRUSTED {
temp: INT;
[pagesAllocated, pagesFreed, pagesInPartition]
← VMStatistics.VirtualAllocation[mds];
temp ← MAX[0, pagesInPartition - pagesAllocated + pagesFreed];
watchStats.mdsFree ← temp;
[pagesAllocated, pagesFreed, pagesInPartition]
← VMStatistics.VirtualAllocation[normalVM];
temp ← MAX[0, pagesInPartition - pagesAllocated + pagesFreed];
watchStats.vmFree ← temp;
};
IF millisSinceLastBigBang >= longPause
AND
NOT container.iconic
THEN {
Calculate the stats for the max VM run (but only if they will be displayed). We do this infrequently since it it relatively expensive.
millisSinceLastBigBang ← 0;
[] ←
VM.Allocate[pagesInPartition+pagesInPartition
! VM.CantAllocate => {watchStats.vmRun ← bestInterval.count; CONTINUE};
];
Calculate the # of GFIs free
watchStats.gfiFree ← CountGFI[];
To avoid a spike in the CPU usage (which can make it look like we are not idle when we really are), we sample the idle count and clock pulses again here.
mark ← BasicTime.GetClockPulses[];
lastIdle ← watchStats.idleCount;
};
};
IF newOpenHeight # oldOpenHeight
THEN {
The desired open height has changed, so actually make the change here.
ViewerOps.SetOpenHeight[container, oldOpenHeight ← newOpenHeight];
IF
NOT container.iconic
THEN
ViewerOps.ComputeColumn[ViewerOps.ViewerColumn[container]];
};
Display Free line: disk, mds, gfi, VM, VM run
SetLabel[diskLabel, watchStats.diskFree ← File.GetVolumePages[File.SystemVolume[]].free];
SetLabel[mdsLabel, watchStats.mdsFree];
SetLabel[gfiLabel, watchStats.gfiFree];
SetLabel[freeVMLabel, watchStats.vmFree];
SetLabel[maxFreeLabel, watchStats.vmRun];
{
Display gc interval, button & status & CPU avg. rate
SetLabel[wordsLabel, words];
SetLabel[diskIOLabel, diskIO];
IF oldDiskPercent # diskPercent
OR oldWordsRate # wordsRate
OR oldIdleRate # idleRate
THEN {
oldIdleRate ← idleRate;
GraphSet[
graph,
oldWordsRate ← wordsRate,
watchStats.cpuLoad,
oldDiskPercent ← diskPercent];
};
SetLabel[decayCpuLabel, Real.RoundI[decayCpuRate*100.0]];
};
IF container.wh > smallerOpenHeight
THEN {
There is more than the minimum amount of information on the screen, so we try to update those numbers as well.
Idle info
SetLabel[minutesIdleLabel, minutesSpentIdle];
SetLabel[millisIdleLabel, millisSpentIdle];
Disk stats
SetLabel[diskPercentQueuedLabel, Real.RoundI[oldDiskPercent*100.0]];
SetLabel[
diskActiveSecsLabel,
(BasicTime.PulsesToMicroseconds[oldActiveDiskPulses]+500000)/1000000];
SetLabel[
diskTotalSecsLabel,
(BasicTime.PulsesToMicroseconds[oldTotalDiskPulses]+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
SetFileStats[FileStats.GetData[open], fsOpenTriplet];
SetFileStats[FileStats.GetData[create], fsCreateTriplet];
SetFileStats[FileStats.GetData[delete], fsDeleteTriplet];
SetFileStats[FileStats.GetData[extend], fsExtendTriplet];
SetFileStats[FileStats.GetData[contract], fsContractTriplet];
SetFileStats[FileStats.GetData[read], fsReadTriplet];
SetFileStats[FileStats.GetData[write], fsWriteTriplet];
};
Lastly, wait for the pause interval
WaitForUpdate[];
ENDLOOP;