<> <> <> <> <<>> DIRECTORY BasicTime USING [GetClockPulses, Now, Pulses, PulsesToMicroseconds, PulsesToSeconds, SetTime, Unpack, Unpacked], Booting USING [CallBootingProcs], Buttons USING [Button, ButtonProc, Create, SetDisplayStyle], CedarProcess USING [SetPriority], Containers USING [ChildXBound, Create], DebuggerSwap USING [WorryCallDebugger], DefaultRemoteNames USING [Get], Disk USING [GetStatistics], EthernetDriverStats USING [EtherStats, EtherStatsRep, GetEthernetOneStats, GetEthernetStats], File USING [SystemVolume], FileBackdoor USING [GetVolumePages], FileStats USING [Data, GetData], FS USING [Error, ExpandName], FSBackdoor USING [NextRemoteEvent, RemoteEvent, RemoteOp], Imager USING [black, Color, MakeGray, MaskBox, SetColor, white], Labels USING [Create, Label, Set, SetDisplayStyle], Idle USING [IsIdle], Icons USING [IconFlavor, NewIconFromFile], IO USING [PutFR], Menus USING [MenuProc], NumberLabels USING [CreateNumber, NumberLabel, NumberLabelUpdate], PrincOps USING [GFT, GFTItem, PageCount, SD, sGFTLength], PrincOpsUtils USING [ReadWDC, WriteWDC], Process USING [Detach, MsecToTicks, Pause, SecondsToTicks, SetTimeout], ProcessorFace USING [PowerOff], Real USING [Round, RoundI], Rope USING [Concat, Find, Length, ROPE, Substr], SafeStorage USING [NWordsAllocated, ReclaimCollectibleObjects, ReclamationReason, SetCollectionInterval, WaitForCollectorDone, WaitForCollectorStart], UserProfile USING [Boolean, CallWhenProfileChanges, Number, ProfileChangedProc], VFonts USING [StringWidth], 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, Booting, Buttons, CedarProcess, Containers, DebuggerSwap, DefaultRemoteNames, Disk, EthernetDriverStats, File, FileBackdoor, FileStats, FS, FSBackdoor, Imager, Labels, Idle, Icons, IO, NumberLabels, PrincOpsUtils, Process, ProcessorFace, Real, Rope, SafeStorage, UserProfile, VFonts, ViewerOps, ViewerSpecs, VM, VMStatistics EXPORTS WatchStats = BEGIN <<**** Useful types from other interfaces ****>> Button: TYPE = Buttons.Button; Label: TYPE = Labels.Label; NumberLabel: TYPE = NumberLabels.NumberLabel; RemoteEventHandle: TYPE = REF READONLY FSBackdoor.RemoteEvent; RemoteOp: TYPE = FSBackdoor.RemoteOp; ROPE: TYPE = Rope.ROPE; <<**** Useful local types and constants ****>> 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]; IconArray: TYPE = REF IconArrayRep; IconArrayRep: TYPE = ARRAY [0..7] OF Icons.IconFlavor; <<**** The following stats are exported via GetWatchStats ****>> watchStats: WatchStats.WatchStatsRecord; GetWatchStats: PUBLIC ENTRY PROC RETURNS [WatchStats.WatchStatsRecord] = { RETURN [watchStats]; }; <<**** Global variables for Watch ****>> 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; middleOpenHeight: 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; minutesSinceTimeSet: INT _ 0; idleCpuRate: REAL _ 0.05; -- all samples must remain below this rate decayCpuRate: REAL _ 0.0; iconArray: IconArray _ NIL; <<**** Procedures for Watch ****>> myGrey: Imager.Color ~ Imager.MakeGray[0.5]; GraphPaint: ViewerClasses.PaintProc = { 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 => { Imager.SetColor[context, myGrey]; Imager.MaskBox[context, [0, 2*self.ch/3, data.top.value, self.ch]]; }; smaller => { Imager.SetColor[context, Imager.white]; Imager.MaskBox[context, [data.top.value, 2*self.ch/3, self.cw, self.ch]]; }; ENDCASE; SELECT data.middle.updateHint FROM bigger => { Imager.SetColor[context, myGrey]; Imager.MaskBox[context, [0, self.ch/3, data.middle.value, 2*self.ch/3]]; }; smaller => { Imager.SetColor[context, Imager.white]; Imager.MaskBox[context, [data.middle.value, self.ch/3, self.cw, 2*self.ch/3]]; }; ENDCASE; SELECT data.bottom.updateHint FROM bigger => { Imager.SetColor[context, myGrey]; Imager.MaskBox[context, [0, 0, data.bottom.value, self.ch/3]]; }; smaller => { Imager.SetColor[context, Imager.white]; Imager.MaskBox[context, [data.bottom.value, 0, self.cw, self.ch/3]]; }; ENDCASE; Imager.SetColor[context, Imager.black] }; Log10: PROC [x: REAL] RETURNS [lx: REAL] = { <> < 0>> < log(x) {absolute error < .0007}>> < 1e6 => 6>> <> 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 ViewerOps.PaintViewer[graph, client, FALSE, $Update]; }; AdjustParameter: Menus.MenuProc = { <<[parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL]>> 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 _ FSBackdoor.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 { <> CedarProcess.SetPriority[idle]; WHILE NOT quit DO watchStats.idleCount _ watchStats.idleCount + 1; IF PrincOpsUtils.ReadWDC[] # 0 THEN TRUSTED { DebuggerSwap.WorryCallDebugger["Idle with interrupts disabled?"]; <> PrincOpsUtils.WriteWDC[0]; <> }; 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 { <<[parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL]>> 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: INT; 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)", [integer[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 = { <<[parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL]>> ENABLE UNWIND => NULL; viewer: ViewerClasses.Viewer _ NARROW[parent]; SELECT mouseButton FROM red => newOpenHeight _ smallerOpenHeight; yellow => newOpenHeight _ middleOpenHeight; blue => newOpenHeight _ biggerOpenHeight; ENDCASE; ForceSampleEntry[]; }; ForceSampleEntry: ENTRY PROC = { <<[parent: REF, clientData: REF, mouseButton: MouseButton, shift, control: BOOL]>> ENABLE UNWIND => NULL; millisSinceLastBigBang _ longPause; BROADCAST waitCond; }; ProfileChanged: UserProfile.ProfileChangedProc = { <<[reason: UserProfile.ProfileChangeReason]>> defaultInterval _ UserProfile.Number["Watch.GCInterval", defaultInterval]; defaultSample _ UserProfile.Number["Watch.SamplePause", 2]; longPause _ UserProfile.Number["Watch.LongPause", 30] * 1000; idleMinutesTilPowerOff _ UserProfile.Number["Watch.idleMinutesTilPowerOff", 10]; IF idleMinutesTilPowerOff < 5 THEN idleMinutesTilPowerOff _ 5; IF UserProfile.Boolean["Watch.powerOffInhibit", FALSE] THEN idleMinutesTilPowerOff _ 1000000; 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", 5]*1E-2; SELECT idleCpuRate FROM < 0.0 => idleCpuRate _ 0.0; > 100.0 => idleCpuRate _ 100.0; ENDCASE; minutesSinceTimeSet _ UserProfile.Number["Watch.minutesSinceTimeSet", 15]; IF minutesSinceTimeSet <= 0 THEN minutesSinceTimeSet _ LAST[INT]; }; TestForIdle: PROC [deltaMillis: INT] = { <> millisSpentIdle _ millisSpentIdle + deltaMillis; WHILE millisSpentIdle >= 60*LONG[1000] DO minutesSpentIdle _ minutesSpentIdle + 1; millisSpentIdle _ millisSpentIdle - 60*LONG[1000]; ENDLOOP; IF Idle.IsIdle[] 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 { <> IF Booting.CallBootingProcs[] = NIL THEN ProcessorFace.PowerOff[]; }; }; }; BlinkBase: PROC [viewer: ViewerClasses.Viewer] = { DO IF viewer.destroyed THEN EXIT; viewer.icon _ iconArray[0]; IF viewer.iconic THEN ViewerOps.PaintViewer[viewer, all]; Process.Pause[Process.SecondsToTicks[10]]; IF NOT viewer.iconic THEN LOOP; FOR i: NAT IN [0..7] DO viewer.icon _ iconArray[i]; IF viewer.iconic THEN ViewerOps.PaintViewer[viewer, all] ELSE EXIT; Process.Pause[Process.MsecToTicks[100]]; ENDLOOP; FOR i: NAT DECREASING IN [0..7] DO viewer.icon _ iconArray[i]; IF viewer.iconic THEN ViewerOps.PaintViewer[viewer, all] ELSE EXIT; Process.Pause[Process.MsecToTicks[100]]; ENDLOOP; ENDLOOP; }; 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; words, wordsRate, oldWordsRate: INT _ 0; oldActiveDiskPulses, oldTotalDiskPulses: BasicTime.Pulses _ 0; oldDiskPercent: REAL _ 0.0; etherStatsData: REF EtherStatsData _ NIL; EtherStatsData: TYPE = RECORD [ ref: EthernetDriverStats.EtherStats _ NIL, old: EthernetDriverStats.EtherStatsRep, time: BasicTime.Pulses, packetsIn, wordsInRate, wordsIn: Labels.Label _ NIL, packetsOut, wordsOutRate, wordsOut: Labels.Label _ NIL]; maxIdleRate: INT _ 1; oldIdleRate, idleRate, lastIdle: INT _ 0; mark: BasicTime.Pulses; millisSinceTimeSet: INT _ 0; 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]; }; UpdateEtherStats: PROC = { IF etherStatsData # NIL THEN { <> sample: EthernetDriverStats.EtherStatsRep _ etherStatsData.ref^; time: BasicTime.Pulses _ BasicTime.GetClockPulses[]; delta: REAL _ BasicTime.PulsesToSeconds[time - etherStatsData.time]; SetLabel[etherStatsData.packetsIn, sample.packetsRecv]; SetLabel[etherStatsData.wordsInRate, Real.Round[(sample.wordsRecv - etherStatsData.old.wordsRecv)/delta]]; SetLabel[etherStatsData.wordsIn, sample.wordsRecv]; SetLabel[etherStatsData.packetsOut, sample.packetsSent]; SetLabel[etherStatsData.wordsOutRate, Real.Round[(sample.wordsSent - etherStatsData.old.wordsSent)/delta]]; SetLabel[etherStatsData.wordsOut, sample.wordsSent]; etherStatsData.old _ sample; etherStatsData.time _ time; }; }; BuildTool: PROC = { <> graphClass: ViewerClasses.ViewerClass = NEW[ViewerClasses.ViewerClassRec _ [paint: GraphPaint, destroy: MyDestroy]]; gapX: INTEGER _ 2; gapY: INTEGER _ 0; lastX: INTEGER _ 0; 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]; }; AddEtherStats: PROC = { ref: EthernetDriverStats.EtherStats _ EthernetDriverStats.GetEthernetOneStats[0]; IF ref = NIL THEN ref _ EthernetDriverStats.GetEthernetStats[0]; IF ref = NIL THEN RETURN; etherStatsData _ NEW[EtherStatsData]; etherStatsData.ref _ ref; etherStatsData.old _ ref^; etherStatsData.time _ BasicTime.GetClockPulses[]; etherStatsData.packetsIn _ AddLabel["Ether packets in", 10]; etherStatsData.wordsInRate _ AddLabel["wds/sec", 6]; etherStatsData.wordsIn _ AddLabel["words", 11, TRUE]; etherStatsData.packetsOut _ AddLabel["Ether packets out", 9]; etherStatsData.wordsOutRate _ AddLabel["wds/sec", 6]; etherStatsData.wordsOut _ AddLabel["words", 11, TRUE]; }; 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]; }; <> container _ Containers.Create[info: [name: "Watch", iconic: TRUE, icon: iconArray[0], 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]; <> gapY _ 1; [] _ 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]; middleOpenHeight _ readFSlabel.wy + readFSlabel.wh * MAX[LONG[1], MIN[LONG[30], UserProfile.Number["Watch.middleLines", 2]]+1]; <> newOpenHeight _ oldOpenHeight _ smallerOpenHeight _ nextY; ViewerOps.SetOpenHeight[container, oldOpenHeight]; <> <<>> <> gapY _ 0; [] _ 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]; <> AddEtherStats[]; <> 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; <> 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[]; TRUSTED { Process.Detach[FORK BlinkBase[container]]; }; <> 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; }; millisSinceLastBigBang _ MIN[longPause, millisSinceLastBigBang + deltaMillis]; millisSinceTimeSet _ millisSinceTimeSet + deltaMillis; IF decayCpuRate < idleCpuRate AND diskPercent = 0.0 AND wordsRate = 0 THEN { TestForIdle[deltaMillis]; IF millisSinceTimeSet/60000 > minutesSinceTimeSet THEN { <> BasicTime.SetTime[]; millisSinceTimeSet _ 0; }; } ELSE minutesSpentIdle _ millisSpentIdle _ 0; <> IF quit OR container.destroyed THEN GO TO done; IF container.iconic OR Idle.IsIdle[] 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 _ FileBackdoor.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 { <> <> UpdateEtherStats[]; <> 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}; }; <<**** Initialization code>> <<>> TRUSTED { iconFileName: ROPE _ FS.ExpandName["Watch.icons"].fullFName; iconArray _ NEW[IconArrayRep _ ALL[tool]]; FOR i: NAT IN [0..7] DO iconArray[i] _ Icons.NewIconFromFile[iconFileName, i ! FS.Error => EXIT]; ENDLOOP; UserProfile.CallWhenProfileChanges[ProfileChanged]; Process.Detach[FORK Watcher]; }; END.