DIRECTORY
BasicTime USING [GetClockPulses, Now, Pulses, PulsesToMicroseconds, Unpack, Unpacked],
Buttons USING [Button, ButtonProc, Create, SetDisplayStyle], 
Containers USING [ChildXBound, Create],
DebuggerSwap USING [WorryCallDebugger],
DefaultRemoteNames USING [Get],
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],
IdleExtras USING [IdleHandler, RegisterIdleHandler],
IO USING [PutFR],
Menus USING [MenuProc], 
NumberLabels USING [CreateNumber, NumberLabel, NumberLabelUpdate],
PrincOps USING [GFT, GFTItem, PageCount, SD, sGFTLength],
Process USING [Detach, MsecToTicks, Pause, SecondsToTicks, SetPriority, SetTimeout],
ProcessorFace USING [PowerOff],
Real USING [RoundI],
Rope USING [Concat, Find, Length, ROPE, Substr],
SafeStorage USING [NWordsAllocated, ReclaimCollectibleObjects, ReclamationReason, SetCollectionInterval, WaitForCollectorDone, WaitForCollectorStart],
UserProfile USING [CallWhenProfileChanges, Number, ProfileChangedProc],
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, DebuggerSwap, DefaultRemoteNames, Disk, File, FileStats, FSExtras, Graphics, Labels, IdleExtras, IO, NumberLabels, Process, ProcessorFace, Real, Rope, SafeStorage, UserProfile, ViewerOps, ViewerSpecs, VM, VMStatistics
EXPORTS WatchStats
= BEGIN

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;

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

watchStats: WatchStats.WatchStatsRecord;
GetWatchStats: PUBLIC ENTRY PROC RETURNS [WatchStats.WatchStatsRecord] = {
RETURN [watchStats];
};

quit: BOOLEAN _ FALSE; -- watched by various processes for exit notification
waitCond: CONDITION;
fsPause: CONDITION _ [timeout: Process.MsecToTicks[200]];
newOpenHeight: INTEGER _ 0;
oldOpenHeight: INTEGER _ 0;
smallerOpenHeight: INTEGER _ 0;
biggerOpenHeight: INTEGER _ 0;
defaultInterval: INT _ 16000;
defaultSample: INT _ 2;
longPause: INT _ 30 * 1000;
millisSinceLastBigBang: INT _ longPause;
powerOffAfter: INT _ 1900;
powerOffBefore: INT _ 700;
idleMinutesTilPowerOff: INT _ 10;
minutesSpentIdle: INT _ 0;
millisSpentIdle: INT _ 0;
idleStatus: {asleep, awake} _ awake;
idleCpuRate: REAL _ 0.04;  -- all samples must remain below this rate
decayCpuRate: REAL _ 0.0;

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] = {
sqrt10: REAL = 3.162278; 
t: REAL;
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};
IF x > sqrt10 THEN {x _ x*(1/sqrt10); lx _ lx + 0.5}; 
t _ (x - 1)/(x + 1); 
lx _ lx + (0.86304 + 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];
}
}; 

AdjustParameter: Menus.MenuProc = {
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] = {
IF NOT quit AND NOT label.destroyed THEN
NumberLabels.NumberLabelUpdate[label, value];
};

FindPosAfter: PROC [base: ROPE, object: ROPE, pos: INT _ 0] RETURNS [INT] = {
start: INT _ Rope.Find[s1: base, pos1: pos, s2: object, case: FALSE];
IF start < 0 THEN RETURN [pos];
RETURN [start + object.Length[]];
};

WaitForFSUpdate: ENTRY PROC = {WAIT fsPause};

DisplayFSStatus: PROC [readFSlabel, writeFSlabel, flushFSlabel, NreadFSlabel, NwriteFSlabel, NflushFSlabel: Label] = {
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, DefaultRemoteNames.Get[].systemHost]]];
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;
};

CauseGCHit: Menus.MenuProc = TRUSTED {
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 = 
["client", "clientTandS", "clientNoTrace", "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,
IO.PutFR[" (GC#%g got %g words, %g objs)",
[cardinal[GCnumber]], [cardinal[wrds]], [cardinal[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 = {
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 = {
ENABLE UNWIND => NULL;
viewer: ViewerClasses.Viewer _ NARROW[parent];
IF control THEN newOpenHeight _ smallerOpenHeight;
IF shift THEN newOpenHeight _ biggerOpenHeight;
ForceSampleEntry[];
};

ForceSampleEntry: ENTRY PROC = {
ENABLE UNWIND => NULL;
millisSinceLastBigBang _ longPause;
BROADCAST waitCond;
};

NoticeIdleTransition: IdleExtras.IdleHandler = {
SELECT reason FROM
becomingIdle => idleStatus _ asleep;
becomingBusy => idleStatus _ awake;
ENDCASE => ERROR;
minutesSpentIdle _ millisSpentIdle _ 0;
};

ProfileChanged: UserProfile.ProfileChangedProc = {
defaultInterval _ UserProfile.Number["Watch.GCInterval", 16000];
defaultSample _ UserProfile.Number["Watch.SamplePause", 2];
longPause _ UserProfile.Number["Watch.LongPause", 30] * 1000;
idleMinutesTilPowerOff _ UserProfile.Number["Watch.idleMinutesTilPowerOff", 10];
IF idleMinutesTilPowerOff < 10 THEN idleMinutesTilPowerOff _ 10;
powerOffAfter _ UserProfile.Number["Watch.powerOffAfter", 1900];
SELECT powerOffAfter FROM
< 0 => powerOffAfter _ 0;
> 2400 => powerOffAfter _ 2400;
ENDCASE;
powerOffBefore _ UserProfile.Number["Watch.powerOffBefore", 700];
SELECT powerOffBefore FROM
< 0 => powerOffBefore _ 0;
> 2400 => powerOffBefore _ 2400;
ENDCASE;
idleCpuRate _ UserProfile.Number["Watch.idleCpuRate", 4]*1E-2;
SELECT idleCpuRate FROM
< 0.0 => idleCpuRate _ 0.0;
> 100.0 => idleCpuRate _ 100.0;
ENDCASE;
};

TestForIdle: PROC [deltaMillis: INT] = {
millisSpentIdle _ millisSpentIdle + deltaMillis;
WHILE millisSpentIdle >= 60*LONG[1000] DO
minutesSpentIdle _ minutesSpentIdle + 1;
millisSpentIdle _ millisSpentIdle - 60*LONG[1000];
ENDLOOP;
IF idleStatus = asleep AND minutesSpentIdle >= idleMinutesTilPowerOff THEN {
unpack: BasicTime.Unpacked _ BasicTime.Unpack[BasicTime.Now[]];
minuteTime: INT _ unpack.hour*100+unpack.minute;
IF minuteTime > powerOffAfter OR minuteTime < powerOffBefore THEN {
ProcessorFace.PowerOff[];
};
};
}; 

Watcher: PROC = {
ENABLE ABORTED => GO TO done;
container, graph: ViewerClasses.Viewer _ NIL;
wordsLabel, decayCpuLabel, diskIOLabel: Label _ NIL;
diskLabel, gfiLabel, mdsLabel, freeVMLabel, maxFreeLabel: Label _ NIL;
minutesIdleLabel: Label _ NIL;
millisIdleLabel: Label _ NIL;
readFSlabel, writeFSlabel, flushFSlabel: Label _ NIL;
NreadFSlabel, NwriteFSlabel, NflushFSlabel: Label _ NIL;
diskPercentQueuedLabel, diskActiveSecsLabel, diskTotalSecsLabel: Label _ NIL;
diskReadLabel, diskWriteLabel, diskReadPgsLabel, diskWritePgsLabel: Label _ NIL;
vmFaultsLabel, vmReadOnlyLabel: Label _ NIL;
vmPinnedPagesLabel, vmCheckoutConflictsLabel: Label _ NIL;
rmAllocPassesLabel, rmReclamationsLabel: Label _ NIL;
rmFreeListLabel, rmOldCleanLabel, rmNewCleanLabel, rmDirtyLabel: Label _ NIL;
rmCleanPassesLabel, laundryWakeupsLabel, pagesCleanedLabel: Label _ NIL;
panicLaundryWakeupsLabel, pagesCleanedPanicLabel: Label _ NIL;
uselessLaundryWakeupsLabel, laundryCleanCallsLabel: Label _ NIL;
swapInCallsLabel, swapInVirtualRunsLabel, swapInPhysicalRunsLabel: Label _ NIL;
swapInPagesLabel, swapInAlreadyInLabel: Label _ NIL;
swapInNoReadLabel, swapInReadsLabel: Label _ NIL;
swapInDirtyVictimsLabel, swapInFailedToCleanVictimsLabel: Label _ NIL;
Triplet: TYPE = REF TripletRep;
TripletRep: TYPE = RECORD [calls, pages, msecs: Label];
fsOpenTriplet: Triplet _ NIL;
fsCreateTriplet: Triplet _ NIL;
fsDeleteTriplet: Triplet _ NIL;
fsExtendTriplet: Triplet _ NIL;
fsContractTriplet: Triplet _ NIL;
fsReadTriplet: Triplet _ NIL;
fsWriteTriplet: Triplet _ NIL;
gapX: INTEGER = 2;
gapY: INTEGER = 1;
words, wordsRate, oldWordsRate: INT _ 0;
oldActiveDiskPulses, oldTotalDiskPulses: BasicTime.Pulses _ 0;
oldDiskPercent: REAL _ 0.0;

maxIdleRate: INT _ 1;
oldIdleRate, idleRate, lastIdle: INT _ 0;
mark: BasicTime.Pulses;

pauseParm: Parameter _
NEW[ParameterBlock _ [SetPause, NIL, defaultSample, 0, 1, 64]];
gcParm: Parameter _
NEW[ParameterBlock _ [SetGCInt, NIL, defaultInterval, 0, 1000, 512000]];

SetFileStats: PROC [data: FileStats.Data, trip: Triplet] = {
SetLabel[trip.calls, data.calls];
SetLabel[trip.pages, data.pages];
SetLabel[trip.msecs, (BasicTime.PulsesToMicroseconds[data.pulses]+500)/1000];
};
BuildTool: PROC = {
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]];
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];

container _ Containers.Create[
info: [name: "Watch", iconic: TRUE, column: right, scrollable: FALSE]]; 

ViewerOps.RegisterViewerClass[$BarGraph, graphClass];
graph _ CreateGraph[];

[] _ AddLabel["Free "];
diskLabel _ AddLabel["disk", 6];
gfiLabel _ AddLabel["gfi", 3];
mdsLabel _ AddLabel["mds", 3];
freeVMLabel _ AddLabel["VM", 5];
maxFreeLabel _ AddLabel["VM run", 5, TRUE];

[] _ 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]]};
};

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

newOpenHeight _ oldOpenHeight _ smallerOpenHeight _ nextY + gapY - 1;
ViewerOps.SetOpenHeight[container, oldOpenHeight]; 

[] _ AddLabel["Flushing "];
flushFSlabel _ AddLabel[
"(FS file being flushed)", 0, TRUE, ViewerSpecs.openRightWidth];
Containers.ChildXBound[container, flushFSlabel];
[] _ AddLabel["Storing "];
writeFSlabel _ AddLabel[
"(FS file being stored)", 0, TRUE, ViewerSpecs.openRightWidth];
Containers.ChildXBound[container, writeFSlabel];

[] _ AddLabel["FS "];
NreadFSlabel _ AddLabel["fetches", 5];
NflushFSlabel _ AddLabel["flushes", 5];
NwriteFSlabel _ AddLabel["stores", 5, TRUE];

minutesIdleLabel _ AddLabel["Idle minutes", 5];
millisIdleLabel _ AddLabel["millis", 5, TRUE];

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

[] _ AddLabel["VM "];
vmFaultsLabel _ AddLabel["faults", 7];
vmReadOnlyLabel _ AddLabel["readOnly", 5];
vmPinnedPagesLabel _ AddLabel["pinned", 5];
vmCheckoutConflictsLabel _ AddLabel["conflicts", 4, TRUE];

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

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

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

mark _ BasicTime.GetClockPulses[];
watchStats.idleCount _ 0;
words _ SafeStorage.NWordsAllocated[];
TRUSTED {
Process.Detach[FORK IdleProcess[]];
Process.Detach[FORK DisplayFSStatus[readFSlabel, writeFSlabel, flushFSlabel, NreadFSlabel, NwriteFSlabel, NflushFSlabel]];
};
};

BuildTool[];

WHILE NOT quit AND NOT container.destroyed DO
reads,writes,readPgs,writePgs: INT;
diskPercent: REAL _ 0.0;
deltaMillis: LONG CARDINAL;
diskIO: INT _ 0;

{
nextMark: BasicTime.Pulses = BasicTime.GetClockPulses[];
idleTemp: INT _ watchStats.idleCount;
delta: LONG CARDINAL _ BasicTime.PulsesToMicroseconds[nextMark - mark];
deltaMillis _ (delta + 500) / 1000;
IF deltaMillis <= 10 THEN {
Process.Pause[1];
LOOP};
mark _ nextMark;

idleRate _ (idleTemp-lastIdle) / deltaMillis;
lastIdle _ idleTemp;
IF idleRate > maxIdleRate THEN
IF deltaMillis > 100 THEN maxIdleRate _ idleRate ELSE idleRate _ maxIdleRate;
};


{
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;
};
};

{
deltaWords: INT _ SafeStorage.NWordsAllocated[] - words;
words _ words + deltaWords;
wordsRate _ (deltaWords * 1000 + 500) / deltaMillis;
};

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

IF quit OR container.destroyed THEN GO TO done;

IF container.iconic OR idleStatus = asleep THEN {
Process.Pause[Process.SecondsToTicks[2]];
LOOP;
};
{
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 {
millisSinceLastBigBang _ 0;
[] _ VM.Allocate[pagesInPartition+pagesInPartition
! VM.CantAllocate => {watchStats.vmRun _ bestInterval.count; CONTINUE};
];
watchStats.gfiFree _ CountGFI[];
mark _ BasicTime.GetClockPulses[];
lastIdle _ watchStats.idleCount;
};
};

IF newOpenHeight # oldOpenHeight THEN {
ViewerOps.SetOpenHeight[container, oldOpenHeight _ newOpenHeight];
IF NOT container.iconic THEN
ViewerOps.ComputeColumn[ViewerOps.ViewerColumn[container]];
};
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];
{
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 {

SetLabel[minutesIdleLabel, minutesSpentIdle];
SetLabel[millisIdleLabel, millisSpentIdle];

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

SetLabel[vmFaultsLabel, VMStatistics.pageFaults];
SetLabel[vmReadOnlyLabel, VMStatistics.readOnlyPages];
SetLabel[vmPinnedPagesLabel, VMStatistics.pinnedPages];
SetLabel[vmCheckoutConflictsLabel, VMStatistics.checkoutConflicts];

SetLabel[rmAllocPassesLabel, VMStatistics.rmAllocPasses];
SetLabel[rmReclamationsLabel, VMStatistics.rmReclamations];
SetLabel[rmFreeListLabel, VMStatistics.rmFreeList];
SetLabel[rmOldCleanLabel, VMStatistics.rmOldClean];
SetLabel[rmNewCleanLabel, VMStatistics.rmNewClean];
SetLabel[rmDirtyLabel, VMStatistics.rmDirty];

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

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

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

};
WaitForUpdate[];

ENDLOOP;

GO TO done;
EXITS done => {quit _ TRUE};
};

TRUSTED {
Process.Detach[FORK Watcher];
};

END.



���|��Watch.mesa
Copyright c 1984, 1985 by Xerox Corporation.  All rights reserved.
Andrew Birrell, November 12, 1982 10:07 am
Paul Rovner, March 1, 1983 5:21 pm
Russ Atkinson, January 28, 1985 12:43:05 pm PST

**** Useful types from other interfaces ****
**** Useful local types and constants ****
**** The following stats are exported via GetWatchStats ****
**** Global variables for Watch ****
**** Procedures for Watch ****
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
fast scale to [1..10], biased toward small sizes
scale to [1..1/sqrt10]
magic cubic approximation
[parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL]
There is only one idle process in the system at a time, and the idle process is the only one that runs at priority 0.  The idle process checks for page faults while interrupts are disabled by looking at the wakeup disable counter.
This procedure counts the free GFIs via a linear scan of the table.
[parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL]
[parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL]
[parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL]
[data: REF ANY, reason: IdleExtras.IdleReason]
[reason: UserProfile.ProfileChangeReason]
We have been "idle" for during the last interval, so test for it being enough. 
It is the right time, and we have been idle for long enough, so let's power off the machine to save power.
Free line labels
Idle labels
FS watcher labels
Disk read/write labels
VM labels
Replacement labels
Laundry labels
SwapIn labels
FileStats labels (stored as triplets)
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.
Must do this here, since order of evaluation matters
build enclosing viewer 
line 0: bar graphs
line 1: mds, gfi, disk, freeVM
line 2: gc interval, button & status
line 3: sample button and FS status
set an aesthetically sized open window
Add labels below this line for the BIG size of Watch.

line 4: file being flushed

line 5: file being stored
line 6: FS counts
line 6A: Idle minutes
line 7: Disk stats
line 8: VM stats
line 9: replacement stats
line 10: laundry stats
line 11: SwapIn stats
initialize measurments
Now we come to the main Watcher loop
Get the time delta
Make this follow the sampling of GetClockPulses as quickly as possible
Not enough precision, so wait for the minimum time, then loop
Update the idle rate data
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).
Update the disk numbers
Update the alloc data
Maintain the exponentially decaying CPU rate.
Test for a valid viewer still existing
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).

Sample the new VM & GFI numbers
Calculate the stats for the max VM run (but only if they will be displayed).  We do this infrequently since it it relatively expensive.
Calculate the # of GFIs free
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.
The desired open height has changed, so actually make the change here.

Display Free line: disk, mds, gfi, VM, VM run

Display gc interval, button & status & CPU avg. rate
There is more than the minimum amount of information on the screen, so we try to update those numbers as well.
Idle info
Disk stats
VM stats
Replacement stats
Laundry stats
SwapIn stats
FileStats stuff

Lastly, wait for the pause interval
**** Initialization code ****
Êÿ��˜�šœ
™
Jšœ
Ïmœ7™BJšœ*™*Jšœ"™"Jšœ/™/—J™�codešÏk	˜	Kšœ
žœG˜VKšœžœ0˜=Kšœžœ˜'Kšœ
žœ˜'Kšœžœ˜Kšœžœ˜Kšœžœ ˜*Kšœ
žœ˜ Kšœ	žœ*˜8Kšœ	žœ/˜=Kšœžœ'˜3Kšœžœ$˜4Kšžœžœ	˜Kšœžœ
˜Kšœ
žœ0˜BKšœ	žœžœžœ˜9KšœžœG˜TKšœžœ˜Kšœžœ
˜Kšœžœžœ
˜0Kšœžœ…˜–Kšœžœ6˜GKšœžœ@˜SKšœ
žœ_˜nKšœžœ˜#Kšžœžœ%˜-Kšœ
žœ¸˜Êšœžœ˜$K˜�——šœžœž˜Kšžœ‚žœfžœ˜Kšžœ˜Kšœž˜—J˜�šœ,™,Jšœžœ˜Jšœžœ˜Jšœ
žœ˜-Jšœžœžœžœ˜<Jšœ
žœ˜#Jšžœžœžœ˜—J˜�šœ*™*Jšœžœžœ˜#šœžœžœ˜JšœžœÏc˜*J˜!JšœžœŸ2˜D—Jšœžœ˜,Jšœžœžœ$˜@Jšœžœžœ˜%šœžœžœ˜Jšœžœ
˜Jšœ
˜
Jšœžœ˜——J˜�šœ<™<Jšœ(˜(š
Ïn
œžœžœžœžœ"˜JJšžœ˜J˜——J˜�šœ$™$JšœžœžœŸ5˜LJšœ
ž	œ˜Kšœ	ž	œ'˜9Jšœžœ˜Jšœžœ˜Jšœžœ˜Jšœžœ˜Jšœžœ	˜Jšœžœ˜Jšœžœ
˜Jšœžœ
˜(Jšœžœ˜Jšœžœ˜Jšœžœ˜!Jšœžœ˜Jšœžœ˜J˜$Jšœ
žœ
Ÿ*˜EJšœžœ˜—J˜�šœ™˜'Jšœžœ˜Jšœžœ˜$šžœžœžœ˜J˜P—šžœž˜šœ˜Jšœ&˜&JšœF˜F—šœ
˜
šœ,˜,JšœL˜L——Jšžœ˜—šžœž˜"šœ˜Jšœ&˜&JšœK˜K—šœ˜Jšœ,˜,JšœR˜R—Jšžœ˜—šžœž˜"šœ˜Jšœ&˜&JšœB˜B—šœ
˜
Jšœ,˜,JšœH˜H—Jšžœ˜—Jšœ*˜*J˜J˜�—š
 œžœžœžœžœ˜,šœ™J™
Jšœžœ,™0Jšœ™—Jšœ<™<Jšœžœ
˜Jšœžœ˜Jšœ0™0šžœž˜
Jšœ˜Jšœ˜J˜ J˜ J˜ J˜ J˜ Jšžœ˜—Jšœ™Jšžœžœ$˜6Jšœ™J˜J˜&J˜J˜�—š œžœ(žœ˜?Jšœžœ˜
Jšœžœ
˜'Jšžœ
žœžœžœ˜šœžœ˜!Jšžœ˜Jšžœ-˜1—šœžœ˜3Jšžœ˜Jšžœžœžœ	žœ˜9—J˜šœžœ˜$Jšžœ˜Jšžœ0˜4—šœžœ˜9Jšžœ˜Jšžœžœžœ	žœ˜<—J˜šœžœ˜$Jšžœ˜Jšžœ0˜4—šœžœ˜9Jšžœ˜Jšžœžœžœ	žœ˜<—J˜šžœžœžœžœ˜(Jšœ%žœ˜5Jšœ˜—J˜J˜�—˜#Jšœ	žœžœ,žœ™Nšžœžœžœ˜Jšœžœ	˜.Jšœžœ
˜%Jšœžœ˜šžœ˜šžœ˜šžœ˜Jšžœ˜Jšžœ˜ ——šžœ˜šžœ˜Jšžœ˜Jšžœ˜!———Jšžœžœ˜$Jšžœžœ˜$šžœžœ˜J˜J˜J˜"—J˜—J˜J˜�—š œžœžœ˜3š	žœžœžœžœž˜(Jšœ-˜-—J˜J˜�—š œžœžœ
žœžœžœžœ˜MJšœžœ4žœ˜EJšžœžœžœ˜Jšžœ˜!J˜J˜�—Kš œžœžœžœ
˜-K˜�š œžœa˜vKšœžœ˜Kšœžœ˜Kšœžœ˜Kšœžœ˜Kšœžœ˜šžœž˜
Kšœžœ˜#Kšœžœ˜Kšœžœ˜Kšœ˜Kšœ ˜ Kšœ˜Kšžœžœžœ˜šžœž˜šœ˜Kšœ3˜3Kšœ"˜"Kšœ˜Kšœ˜—šœ˜Kšœ3˜3Kšœ˜Kšœ˜—šœ˜Kšœ5˜5Kšœ$˜$Kšœ˜K˜—šœ˜Kšœ5˜5Kšœ˜Kšœ˜—šœ˜Kšœ5˜5Kšœ$˜$Kšœ˜K˜—šœ˜Kšœ5˜5Kšœ˜K˜—Kšžœžœ˜—KšœV˜VKšœ)˜)Kšœ˜Kšžœ˜—K˜K˜�—š œžœžœ˜Jšœæ™æJ˜šžœžœž˜Jšœ0˜0šžœž˜#J˜A—Jšžœ˜—J˜J˜�—š
 œžœžœžœžœ˜.JšœC™CJ˜	šžœžœž
œžœžœžœ˜GJšœ"žœ˜)Jšžœžœ˜&Jšžœ˜—J˜J˜�—šœžœ˜&JšœN™Nšžœžœžœ˜Jšœžœ	˜.J˜.Jšœ&žœ˜@Jšžœ	žœžœ˜,J˜/J˜—J˜J˜�—š œžœ˜1Jšžœžœžœžœ˜Jšœžœ˜Jšœ
žœ˜šžœžœžœ˜šœ
žœžœžœ˜8˜>J˜@——Jšœžœžœ˜Jšœ
žœ˜Jšœžœ˜J˜NJšžœžœžœ˜Jšžœ<˜C˜SJ˜#—Jšžœžœžœ˜šœ˜Jšœ˜šžœ(˜*Jšœ:˜:—Jšœ˜—Jšžœ˜—Jšžœ˜J˜J˜�—š œžœ˜$J˜3J˜J˜�—šœžœ˜.Jšžœžœžœ˜Jšœžœ˜,Jšœžœ˜
Jšž	œ
˜J˜J˜�—š 
œžœžœ˜Jšžœžœžœ˜Jšžœ
˜˜J˜�——š œžœžœžœ˜2Jšžœžœžœ˜J˜BJšž	œ
˜J˜J˜�—šœ˜JšœN™NJšžœžœžœ˜Jšœžœ	˜.Jšžœ	žœ#˜2Jšžœžœ"˜/Jšœ˜J˜J˜�—š œžœžœ˜ JšœN™NJšžœžœžœ˜Jšœ#˜#Jšž	œ
˜J˜J˜�—•StartOfExpansion2 -- [data: REF ANY, reason: IdleExtras.IdleReason]šœ0˜0JšÐck.™.šžœž˜Jšœ$˜$Jšœ#˜#Jšžœžœ˜—Jšœ'˜'J˜J˜�—–- -- [reason: UserProfile.ProfileChangeReason]šœ2˜2Jš¡)™)Jšœ@˜@Jšœ;˜;Jšœ=˜=J˜PJšžœžœ˜@J˜@šžœž˜Jšœ˜Jšœ˜Jšžœ˜—J˜Ašžœž˜Jšœ˜Jšœ ˜ Jšžœ˜—J˜>šžœ
ž˜Jšœ˜Jšœ˜Jšžœ˜—Jšœ˜J˜�—š œžœžœ˜(JšœO™OJšœ0˜0šžœžœž˜)Jšœ(˜(Jšœ'žœ˜2Jšžœ˜—šžœžœ,žœ˜LJ˜?Jšœžœ!˜0šžœžœžœ˜CJšœj™jJ˜J˜—J˜—J˜J˜�—š œžœ˜Jšžœžœžœžœ˜Jšœ)žœ˜-Jšœ0žœ˜4šœ™JšœBžœ˜F—šœ™Jšœžœ˜Jšœžœ˜—šœ™Jšœ1žœ˜5Jšœ4žœ˜8—šœ™JšœIžœ˜MJšœLžœ˜P—šœ	™	Jšœ(žœ˜,Jšœ6žœ˜:—šœ™Jšœ1žœ˜5JšœIžœ˜M—šœ™JšœDžœ˜HJšœ:žœ˜>Jšœ<žœ˜@—šœ
™
JšœKžœ˜OJšœ0žœ˜4Jšœ-žœ˜1JšœBžœ˜F—šœ%™%Jšœ	žœžœ˜Jšœžœžœ˜7Jšœžœ˜Jšœžœ˜Jšœžœ˜Jšœžœ˜Jšœžœ˜!Jšœžœ˜Jšœžœ˜—Jšœžœ˜Jšœžœ˜Jšœ žœ˜(J˜>Jšœžœ˜J˜�Jšœ
žœ˜Jšœ!žœ˜)Jšœ˜J˜�˜Jšžœžœ˜?—˜Jšžœžœ%˜HJ˜�—š œžœ*˜<Jšœ!˜!Jšœ!˜!JšœM˜MJ˜—š 	œžœ˜Jšœï™ï˜'JšžœI˜L—Jšœžœ˜Jšœžœ˜Jšœžœ˜š œžœ
žœ	žœžœžœžœžœ˜sšžœ
˜šžœ˜šœ˜šœ˜Jšœ)žœ ˜N—Jšœžœ˜—J˜šœ"˜"Jšœ
žœžœ˜JJšœ
˜
Jšœžœ˜—J˜—šž˜˜šœ˜Jšœ)žœ ˜N—Jšœžœ˜———J˜+šžœžœ˜J˜2—J˜—š
 
œžœžœžœžœ˜FJšžœžœžœžœ˜5Jšœžœ%žœžœ˜:Jšœ4™4Jšœ"˜"Jšœ"žœ˜(J˜—š 	œžœžœžœ	žœžœžœžœžœžœ˜®Jšœžœ˜"Jšžœžœžœ˜*˜JšœJžœ˜PJšœžœ'žœ˜8—J˜šžœžœž˜šœ˜šœ"˜"Jšœ
žœžœ˜JJšœ
˜
Jšœžœ˜——šœžœ˜%Jšœ
žœžœ˜JJšœžœ˜—Jšžœ˜—šžœ	žœžœ˜J˜Jšžœžœžœ˜&J˜—J˜šžœžœ˜J˜
J˜&—J˜—š œžœžœ	žœ˜2KšœQžœ
žœ˜gK˜—š œžœžœ"˜;Jš œžœžœžœžœžœ˜IJšœžœ˜Jšœžœ˜Jšœ"žœ˜7Jšœ(žœžœ˜IJšœ$žœžœ˜E˜ J˜˜%J˜OJ˜5JšœžœF˜O—Jšœžœ˜—J˜Jšœ˜Jšœ.˜.Jšœ8˜8Jšœ<˜<Jšœ?˜?Jšœ@˜@J˜Jšœ˜Jšœ.˜.Jšœ9˜9Jšœ;˜;Jšœ;˜;JšžœŸ˜J˜�—Jšœ3˜3šœ:˜:J˜�—Jšœ™˜Jšœžœžœ˜HJ˜�—Jšœ™Jšœ5˜5J˜J˜�Jšœ™J˜J˜ J˜J˜J˜ Jšœ%žœ˜+J˜�Jšœ$™$JšœB˜BJ˜%J˜Jšœ3˜3šœ˜Jšœ0žœ˜RJ˜1Jšžœžœ#˜?Jšœ˜—J˜�Jšœ#™#J˜8JšœB˜BJ˜+J˜šœ˜Jšœ$žœ˜F—Jšœ/˜/J˜�Jšœ&™&JšœE˜EJšœ3˜3J˜�J™5J™�Jšœ™J˜šœ˜Jšœžœ˜@—Jšœ0˜0J™�Jšœ™J˜šœ˜Jšœžœ˜?—Jšœ0˜0J˜�Jšœ™J˜Jšœ&˜&Jšœ'˜'Jšœ&žœ˜,J˜�Jšœ™Jšœ/˜/Jšœ(žœ˜.J˜�Jšœ™J˜Jšœ/˜/Jšœ/˜/Jšœ/žœ˜5Jšœ'˜'Jšœ'˜'Jšœ'˜'Jšœ(žœ˜.J˜�Jšœ™J˜Jšœ&˜&Jšœ*˜*Jšœ+˜+Jšœ4žœ˜:J˜�Jšœ™J˜Jšœ+˜+Jšœ+žœ˜1Jšœ(˜(Jšœ%˜%Jšœ%˜%Jšœ$žœ˜*J˜�Jšœ™J˜Jšœ+˜+Jšœ)˜)Jšœ-žœ˜3Jšœ2˜2Jšœ1˜1Jšœ3˜3Jšœ4žœ˜:J˜�Jšœ™J˜Jšœ(˜(Jšœ.˜.Jšœ/žœ˜5Jšœ*˜*Jšœ0˜0Jšœ)˜)Jšœ'žœ˜-Jšœ8˜8Jšœ=žœ˜CšœŸ˜Jšœžœ˜Jšœ$žœ˜*Jšœ!˜!Jšœžœ˜$Jšœ-˜-Jšœ-˜-Jšœ-˜-Jšœ1˜1Jšœ)˜)Jšœ+˜+J˜—J˜�Jšœ$˜$J˜�Jšœ™Jšœ"˜"Jšœ˜J˜&šžœ˜	Kšœžœ˜#Kšœžœg˜zKšœ˜—J˜J˜�—J˜J˜�Jšœ$™$š	žœžœžœžœž˜-Jšœžœ˜#Jšœ
žœ˜Jšœ
žœžœ˜Jšœžœ˜J˜�˜Jšœ™Jšœ8˜8šœ
žœ˜%JšœF™F—Jšœžœžœ3˜GJšœ#˜#šžœžœ˜Jšœ=™=Jšœ˜Jšžœ˜—Jšœ˜J˜�Jšœ™Jšœ-˜-Jšœ˜šžœž˜Jšœ¥™¥Jšžœžœžœ˜M—J˜—˜�J˜�—šœ˜Jšœ™J˜:Kšœ˜˜˜Jšœ˜šžœ)žœ˜1šžœ+žœ˜3šœ=˜=Jšœ*˜*—Jšœ*˜*J˜—Jšœ(˜(J˜—J˜—J˜�˜Jšœ™Jšœžœ)˜8J˜J˜4J˜—J˜�˜Jšœ-™-Jšœžœ˜ Jšžœžœ˜Jšœ4˜4JšœA˜AJ˜J˜�—šžœžœžœ˜EJšžœ˜Jšžœ(˜,J˜�—Jšœžœ2˜NJ˜�J™&Jš
žœžœžœžœžœ˜/J˜�šžœžœžœ˜1Jšœž™žJ˜)Jšžœ˜Jšœ˜—J™�šœ˜Jšœ™Jšœ.žœ˜;šžœ˜	Jšœžœ˜
šœ.˜.Jšœ&˜&—Jšœžœ4˜>Jšœ˜šœ.˜.Jšœ+˜+—Jšœžœ4˜>Jšœ˜J˜—šžœ%žœžœžœ˜FJšœ‡™‡Jšœ˜šœžœ+˜2Jšœžœ9žœ˜GJšœ˜—Jšœ™Jšœ ˜ Jšœ™™™Jšœ"˜"Jšœ ˜ J˜—Jšœ˜—J˜�šžœžœ˜'JšœF™FJšœB˜Bšžœžœž˜Jšœ;˜;—J˜—J™�Jšœ-™-JšœY˜YJšœ'˜'Jšœ'˜'Jšœ)˜)Jšœ)˜)J™�šœ˜Jšœ4™4J˜J˜šžœžœžœžœ˜\Jšœ˜˜	J˜J˜Jšœ˜Jšœ˜—J˜—Jšœ9˜9J˜—J˜�šžœ"žœ˜*Jšœn™nJ˜�Jšœ	™	Jšœ-˜-Jšœ+˜+J˜�Jšœ
™
JšœD˜Dšœ	˜	Jšœ˜JšœF˜F—šœ	˜	Jšœ˜JšœE˜E—Jšœ˜Jšœ!˜!Jšœ$˜$Jšœ&˜&J˜�Jšœ™Jšœ1˜1Jšœ6˜6Jšœ7˜7JšœC˜CJ˜�Jšœ™Jšœ9˜9Jšœ;˜;Jšœ3˜3Jšœ3˜3Jšœ3˜3Jšœ-˜-J˜�Jšœ
™
Jšœ9˜9Jšœ;˜;Jšœ7˜7JšœE˜EJšœA˜AJšœI˜IJšœA˜AJ˜�Jšœ™Jšœ5˜5JšœA˜AJšœC˜CJšœ5˜5Jšœ=˜=Jšœ7˜7Jšœ5˜5JšœC˜CJšœS˜SJ˜�Jšœ™Jšœ5˜5Jšœ9˜9Jšœ9˜9Jšœ9˜9Jšœ=˜=Jšœ5˜5šœ7˜7J˜�—J˜—J™�Jšœ#™#J˜J˜�Jšžœ˜J˜�—Jšžœžœ˜Jšžœžœ˜J˜——J˜�šœ™šžœ˜	Jšœžœ
˜Jšœ˜——J˜�šžœ˜J˜�J˜�J˜�——�…—����pÒ��ŸM��