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˜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šœžœ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