DIRECTORY Buttons USING [Button, ButtonProc, Create, SetDisplayStyle], CIFSFeedback USING [Register], Containers USING [ChildXBound, Create], Convert USING [ValueToRope], DiskDriverSharedImpl USING [totalNumberOfRequests], File USING [Capability, nullCapability, ShowCapability], Imager USING [black, MaskIntRectangle, SetColor, white], Labels USING [Create, Label, Set, SetDisplayStyle], NumberLabels USING [CreateNumber, NumberLabel, NumberLabelUpdate], PrincOpsRuntime USING [GFT, GFTItem], Process USING [Detach, Pause, SecondsToTicks, SetPriority, SetTimeout], Real USING [RoundI], Rope USING [Cat, Concat, Find, Length, Replace, ROPE], RTProcess USING [GetTotalPageFaults, StartWatchingFaults, StopWatchingFaults], RTQuanta USING [PagesPerQuantum], RTRefCounts USING [AMapOiOe, AMapPiRce, GetMapOiOe, GetMapPiRce, mapOiOeLimit, mapOiOeStart, OverflowEntry, ProbeSize, rceEmpty, RCEntry, rcFinalize, RefCt], RTZones USING [MapQZf, mzVacant, TMapQZf, ZoneFinger], Runtime USING [IsBound], SafeStorage USING [NWordsAllocated, ReclaimCollectibleObjects, ReclamationReason, TrimAllZones, TrimRootBase, SetCollectionInterval, WaitForCollectorDone, WaitForCollectorStart], SDDefs USING [SD, sGFTLength], ShowTime USING [GetMark, Microseconds], Space USING [GetAttributes, GetHandle, GetWindow, Handle, mds, nullHandle, PageFromLongPointer, PageNumber, virtualMemory], System USING [GetGreenwichMeanTime, gmtEpoch, GreenwichMeanTime], UserProfile USING [Number], ViewerClasses USING [DestroyProc, PaintProc, Viewer, ViewerClass, ViewerClassRec], ViewerOps USING [ComputeColumn, CreateViewer, PaintViewer, RegisterViewerClass, SetOpenHeight, ViewerColumn], ViewerSpecs USING [openRightWidth], Volume USING [GetAttributes, SystemID]; Watch: CEDAR MONITOR IMPORTS Buttons, CIFSFeedback, Containers, Convert, DiskDriverSharedImpl, File, Imager, Labels, NumberLabels, Process, Real, Rope, RTProcess, RTRefCounts, RTZones, Runtime, SafeStorage, ShowTime, Space, System, UserProfile, ViewerOps, ViewerSpecs, Volume SHARES DiskDriverSharedImpl = BEGIN Button: TYPE = Buttons.Button; Label: TYPE = Labels.Label; NumberLabel: TYPE = NumberLabels.NumberLabel; ROPE: TYPE = Rope.ROPE; NormalSize: CARDINAL = RTRefCounts.ProbeSize; OverflowSize: CARDINAL = RTRefCounts.mapOiOeLimit-1; OiRange: TYPE = [RTRefCounts.mapOiOeStart..RTRefCounts.mapOiOeStart+OverflowSize); GraphData: TYPE = REF GraphDataRec; GraphEntry: TYPE = RECORD [ value: INTEGER _ 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: Labels.Label, value,increment,lo,hi: INT]; WatchStats: TYPE = RECORD [ diskFree, gfiFree, mdsFree: INT _ 0, vmFree, vmRun: INT _ 0, vmCedar, vmZones: INT _ 0, seconds: INT _ 0, cpuLoad: REAL _ 0.0 ]; watchStats: WatchStats; cifsLabel: Labels.Label _ NIL; cifsChanges: LIST OF Rope.ROPE _ NIL; cifsPause: CONDITION _ [timeout: Process.SecondsToTicks[1]]; cifsWait: CONDITION; idleCount: INT _ 0; quit: BOOLEAN _ FALSE; -- watched by various processes for exit notification waitCond: CONDITION; lastVMPoll: System.GreenwichMeanTime _ System.gmtEpoch; newOpenHeight: INTEGER _ 0; oldOpenHeight: INTEGER _ 0; smallerOpenHeight: INTEGER _ 0; biggerOpenHeight: INTEGER _ 0; GraphPaint: ViewerClasses.PaintProc = { OPEN Imager; 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 => TRUSTED { SetColor[context, black]; MaskIntRectangle[context, [0, 2*self.ch/3, data.top.value, self.ch/3]]}; smaller => { SetColor[context, white]; MaskIntRectangle[context, [data.top.value, 2*self.ch/3, self.cw, self.ch/3]]}; ENDCASE; SELECT data.middle.updateHint FROM bigger => TRUSTED { SetColor[context, black]; MaskIntRectangle[context, [0, self.ch/3, data.middle.value, self.ch/3]]}; smaller => { SetColor[context, white]; MaskIntRectangle[context, [data.middle.value, self.ch/3, self.cw, self.ch/3]]}; ENDCASE; SELECT data.bottom.updateHint FROM bigger => TRUSTED { SetColor[context, black]; MaskIntRectangle[context, [0, 0, data.bottom.value, self.ch/3]]}; smaller => { SetColor[context, white]; MaskIntRectangle[context, [data.bottom.value, 0, self.cw, self.ch/3]]}; ENDCASE; }; 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*t + 0.36415*(t*t*t) }; GraphSet: PROC [graph: ViewerClasses.Viewer, t, m, b: REAL] = { tempR: REAL; tempI: INTEGER; 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; tempI _ Real.RoundI[tempR]; myData.top.updateHint _ IF tempI > myData.top.value THEN bigger ELSE IF tempI < myData.top.value THEN smaller ELSE same; myData.top.value _ tempI; tempR _ IF myData.middle.fullScale<0 THEN m*graph.cw ELSE Log10[1 + m]*graph.cw/myData.middle.fullScale; tempI _ Real.RoundI[tempR]; myData.middle.updateHint _ IF tempI > myData.middle.value THEN bigger ELSE IF tempI < myData.middle.value THEN smaller ELSE same; myData.middle.value _ tempI; tempR _ IF myData.bottom.fullScale<0 THEN b*graph.cw ELSE Log10[1 + b]*graph.cw/myData.bottom.fullScale; tempI _ Real.RoundI[tempR]; myData.bottom.updateHint _ IF tempI > myData.bottom.value THEN bigger ELSE IF tempI < myData.bottom.value THEN smaller ELSE same; myData.bottom.value _ tempI; IF NOT graph.parent.iconic THEN TRUSTED{ ViewerOps.PaintViewer[graph, client, FALSE, $Update] } }; Decimal: PROC [int: INT] RETURNS [ROPE] = { SELECT int FROM 0 => RETURN ["0"]; 1 => RETURN ["1"]; 2 => RETURN ["2"]; 3 => RETURN ["3"]; ENDCASE; RETURN[Convert.ValueToRope[[signed[int, 10]]]]}; AdjustParameter: Buttons.ButtonProc = { 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, paint: BOOL _ TRUE] = { IF NOT quit THEN { IF label.destroyed THEN RETURN; NumberLabels.NumberLabelUpdate[label, value]; }; }; UpdateCifsStatus: ENTRY PROC [r: ROPE] = TRUSTED { prev: LIST OF Rope.ROPE _ NIL; IF quit THEN RETURN; FOR this: LIST OF Rope.ROPE _ cifsChanges, this.rest UNTIL this = NIL DO prev _ this; ENDLOOP; IF prev = NIL THEN cifsChanges _ CONS[first: r, rest: NIL] ELSE prev.rest _ CONS[first: r, rest: NIL]; NOTIFY cifsWait; }; NextCifsStatus: ENTRY PROC RETURNS [r: ROPE] = TRUSTED { WAIT cifsPause; -- force one second between display changes -- WHILE cifsChanges = NIL AND NOT quit DO WAIT cifsWait ENDLOOP; IF quit THEN {cifsChanges _ NIL; RETURN}; r _ cifsChanges.first; cifsChanges _ cifsChanges.rest; }; FindPosAfter: PROC [base: ROPE, object: ROPE, pos: INT _ 0] RETURNS [INT] = TRUSTED { start: INT _ Rope.Find[s1: base, pos1: pos, s2: object, case: FALSE]; IF start < 0 THEN RETURN [pos]; RETURN [start + object.Length[]]; }; DisplayCifsStatus: PROC = TRUSTED { UNTIL quit DO r: ROPE _ NextCifsStatus[]; IF r.Length[] = 0 THEN Labels.SetDisplayStyle[cifsLabel, $BlackOnWhite] ELSE { pos: INT _ FindPosAfter[r, "Retrieving "]; IF pos # 0 THEN r _ r.Replace[0, FindPosAfter[r, "/Indigo/", pos], "Cifs: "]; Labels.Set[cifsLabel, r]; Labels.SetDisplayStyle[cifsLabel, $WhiteOnBlack]; }; ENDLOOP; cifsLabel _ NIL; cifsChanges _ NIL; }; IdleProcess: PROC = TRUSTED { Process.SetPriority[0]; WHILE NOT quit DO idleCount _ idleCount + 1; ENDLOOP; }; SumZones: PROC RETURNS [inZones: CARDINAL _ 0] = TRUSTED { map: RTZones.TMapQZf _ RTZones.MapQZf; FOR i: NAT IN [0..map.length) DO f: RTZones.ZoneFinger _ map[i]; IF f = RTZones.mzVacant THEN LOOP; inZones _ inZones + RTQuanta.PagesPerQuantum; ENDLOOP; }; SameFile: PROC [cap1,cap2: File.Capability] RETURNS [BOOL] = TRUSTED { RETURN [File.ShowCapability[cap1].fID = File.ShowCapability[cap2].fID]; }; FileFromAddress: PROC [addr: LONG POINTER] RETURNS [File.Capability] = TRUSTED { ENABLE {ABORTED => GO TO abort; ANY => GO TO none}; page: Space.PageNumber _ Space.PageFromLongPointer[addr]; space: Space.Handle _ Space.GetHandle[page]; RETURN [Space.GetWindow[space].file]; EXITS abort => ERROR ABORTED; none => RETURN [File.nullCapability]; }; CountVM: PROC [parent: Space.Handle] RETURNS [ total--pages in use--, largest--largest leaf space--, avg--size of leaf spaces--, count--number of leaf spaces--, totalFree--number of free pages--, largestFree--largest free run--, avgFree--size of free run--, countFree--number of free runs--, mdsFree--number of MDS pages free--, cedarPages--pages mapped to Cedar backing file--, mappedPages--pages mapped to some backing file--, unmappedPages--pages in allocated spaces, but not mapped--, anonPages--pages mapped to anonymous backing file--, unknownPages--pages mapped to some file, but not known which file--, sparePages--spare pages within low-level spaces--, toBoot--pages mapped to boot file--: INT] = { accumForSpace: PROC [space: Space.Handle, level: INTEGER _ 0, tryMapped: BOOL _ TRUE] = TRUSTED { child: Space.Handle; spaceBase, spaceLim, nextStart, pages: CARDINAL; mapped: BOOL; [lowestChild: child, size: pages, base: spaceBase, mapped: mapped] _ Space.GetAttributes[space]; nextStart _ 0; spaceLim _ spaceBase + pages; IF tryMapped AND mapped THEN TRUSTED { file: File.Capability _ File.nullCapability; mappedPages _ mappedPages + pages; {ENABLE {ABORTED => GO TO abort; ANY => GO TO noGood}; file _ Space.GetWindow[space].file; EXITS noGood => unknownPages _ unknownPages + pages; abort => ERROR ABORTED; }; SELECT TRUE FROM SameFile[file, CedarVMFile] => cedarPages _ cedarPages + pages; SameFile[file, File.nullCapability] => anonPages _ anonPages + pages; SameFile[bootFile, File.nullCapability] => { bootFile _ file; toBoot _ toBoot + pages}; SameFile[file, bootFile] => { toBoot _ toBoot + pages}; ENDCASE; }; IF NOT mapped AND child # Space.nullHandle THEN { childSize: CARDINAL _ 0; nextChild: Space.Handle; base: CARDINAL; [] _ Space.GetAttributes[child ! ANY => GO TO forceLeaf]; WHILE child # Space.nullHandle DO [nextSibling: nextChild, size: childSize, base: base] _ Space.GetAttributes[child ! ANY => GO TO moreUnknown]; IF base > nextStart THEN { free: CARDINAL _ base-nextStart; IF level > 1 THEN IF space = Space.mds THEN mdsFree _ mdsFree + free ELSE sparePages _ sparePages + free ELSE { totalFree _ totalFree + free; countFree _ countFree + 1; IF free > largestFree THEN largestFree _ free}; }; nextStart _ base + childSize; accumForSpace[child, level+1, tryMapped AND NOT mapped]; child _ nextChild; ENDLOOP; base _ pages; IF base > nextStart THEN { free: CARDINAL _ base-nextStart; IF level > 1 THEN IF space = Space.mds THEN mdsFree _ mdsFree + free ELSE sparePages _ sparePages + free ELSE { totalFree _ totalFree + free; countFree _ countFree + 1; IF free > largestFree THEN largestFree _ free}; }; RETURN; EXITS forceLeaf => {}; moreUnknown => { unknownPages _ unknownPages + pages - (nextStart-spaceBase); RETURN; }; }; total _ total + pages; count _ count + 1; IF largest < pages THEN largest _ pages; IF tryMapped AND NOT mapped THEN unmappedPages _ unmappedPages + pages; }; bootFile: File.Capability _ File.nullCapability; CedarVMFile: File.Capability _ File.nullCapability; TRUSTED { rope: ROPE _ "rope"; CedarVMFile _ FileFromAddress[LOOPHOLE[rope]]; bootFile _ FileFromAddress[LOOPHOLE[DiskDriverSharedImpl,POINTER]]; }; FOR i: NAT IN [0..10) DO total _ largest _ avg _ count _ totalFree _ largestFree _ avgFree _ countFree _ cedarPages _ mappedPages _ unmappedPages _ anonPages _ unknownPages _ sparePages _ toBoot _ mdsFree _ 0; accumForSpace[parent, 1 ! ABORTED => GO TO abort; ANY => LOOP]; EXIT; ENDLOOP; IF count > 0 THEN avg _ total / count; IF countFree > 0 THEN avgFree _ totalFree / countFree; EXITS abort => ERROR ABORTED; }; CountGFI: PROC RETURNS [free: INT] = TRUSTED { free _ 0; FOR i: CARDINAL DECREASING IN [1..SDDefs.SD[SDDefs.sGFTLength]) DO item: PrincOpsRuntime.GFTItem _ PrincOpsRuntime.GFT[i]; IF item.data # 0 THEN EXIT; free _ free + 1; ENDLOOP; }; GetRCTabStats: PROC RETURNS [ normal, pinned, finalize, chains, normalFree, overflow, overflowFree: INTEGER _ 0] = TRUSTED { mapPiRce: LONG POINTER TO RTRefCounts.AMapPiRce = RTRefCounts.GetMapPiRce[]; rPtr: LONG POINTER TO RTRefCounts.RCEntry _ @mapPiRce[0]; mapOiOe: LONG POINTER TO RTRefCounts.AMapOiOe = RTRefCounts.GetMapOiOe[]; oPtr: LONG POINTER TO RTRefCounts.OverflowEntry _ @mapOiOe[0]; FOR pi: CARDINAL IN [0..NormalSize) DO entry: RTRefCounts.RCEntry = rPtr^; rPtr _ rPtr + SIZE[RTRefCounts.RCEntry]; IF entry = RTRefCounts.rceEmpty THEN {normalFree _ normalFree + 1; LOOP}; WITH e: entry SELECT FROM overflow => chains _ chains + 1; normal => { normal _ normal + 1; SELECT e.rc FROM LAST[RTRefCounts.RefCt] => pinned _ pinned + 1; <= RTRefCounts.rcFinalize => finalize _ finalize + 1; ENDCASE; }; ENDCASE; ENDLOOP; FOR oi: CARDINAL IN OiRange DO oe: RTRefCounts.OverflowEntry = oPtr^; rce: RTRefCounts.RCEntry = oe.rce; oPtr _ oPtr + SIZE[RTRefCounts.OverflowEntry]; WITH e: rce SELECT FROM overflow => overflowFree _ overflowFree + 1; normal => { overflow _ overflow + 1; SELECT e.rc FROM LAST[RTRefCounts.RefCt] => pinned _ pinned + 1; <= RTRefCounts.rcFinalize => finalize _ finalize + 1; ENDCASE; }; ENDCASE; ENDLOOP; }; CountDisk: PROC RETURNS [free: INT] = { [freePageCount: free] _ Volume.GetAttributes[Volume.SystemID[]]; }; CauseGCHit: Buttons.ButtonProc = TRUSTED { IF NOT quit THEN { viewer: ViewerClasses.Viewer _ NARROW[parent]; Buttons.SetDisplayStyle[viewer, $BlackOnGrey]; SafeStorage.ReclaimCollectibleObjects[TRUE, mouseButton # red]; IF shift THEN SafeStorage.TrimAllZones[]; IF control THEN [] _ SafeStorage.TrimRootBase[]; IF control OR shift THEN ForceSampleEntry[]; Buttons.SetDisplayStyle[viewer, $BlackOnWhite]; }; }; CollectorWatcher: PROC [gcStatusLabel: Labels.Label] = { ENABLE ABORTED => GO TO done; active: ROPE = "BUSY: "; trimming: ROPE = "BUSY: trimming zones"; inactive: ROPE = "done."; wordsBetweenTrim: INT _ 0; defaultBetweenTrim: INT _ UserProfile.Number["Watch.BetweenTrim", 256*LONG[1024]]; defaultCedarDelta: INT _ UserProfile.Number["Watch.CedarDelta", 1600]; WHILE NOT quit DO cReasons: ARRAY SafeStorage.ReclamationReason OF ROPE = ["clientRequest", "clientTandSRequest", "clientNoTraceRequest", "rcTableOverflow", "allocationInterval", "quantaNeeded", "finalizationThreshold"]; r: SafeStorage.ReclamationReason; wrds,objs: LONG CARDINAL; GCnumber: CARDINAL; msg: ROPE _ inactive; [reason: r] _ SafeStorage.WaitForCollectorStart[]; IF quit THEN EXIT; TRUSTED{ Labels.Set[gcStatusLabel, active.Concat[cReasons[r]] ] }; [incarnation: GCnumber, reason: r, wordsReclaimed: wrds, objectsReclaimed: objs] _ SafeStorage.WaitForCollectorDone[]; wordsBetweenTrim _ wordsBetweenTrim + wrds; IF quit THEN EXIT; IF watchStats.vmRun < 1000 AND watchStats.vmCedar - watchStats.vmZones > defaultCedarDelta AND defaultBetweenTrim > 0 AND wordsBetweenTrim > defaultBetweenTrim THEN { Labels.Set[gcStatusLabel, trimming]; wordsBetweenTrim _ 0; SafeStorage.TrimAllZones[]; [] _ SafeStorage.TrimRootBase[]; }; TRUSTED{ IF quit THEN EXIT; Labels.Set[ gcStatusLabel, inactive.Cat[ " (GC#", Decimal[GCnumber], " got ", Decimal[LOOPHOLE[wrds]].Concat[" words, "], Decimal[LOOPHOLE[objs]].Concat[" objs)"]]]; }; ENDLOOP; EXITS done => {}; }; SetGCInt: ENTRY PROC [parm: Parameter] = { ENABLE UNWIND => NULL; [] _ SafeStorage.SetCollectionInterval[parm.value]; }; MyDestroy: ENTRY ViewerClasses.DestroyProc = { ENABLE UNWIND => NULL; viewer: ViewerClasses.Viewer _ NARROW[self]; quit _ TRUE; BROADCAST waitCond; }; WaitForUpdate: ENTRY PROC RETURNS[ vmWanted: BOOL ] = { ENABLE UNWIND => NULL; now: System.GreenwichMeanTime; vmInterval: INT _ 60; -- seconds -- interval: INT; WAIT waitCond; now _ System.GetGreenwichMeanTime[]; interval _ now - lastVMPoll; vmWanted _ interval > vmInterval; watchStats.seconds _ watchStats.seconds + interval; }; SetPause: ENTRY PROC [parm: Parameter] = TRUSTED { ENABLE UNWIND => NULL; Process.SetTimeout[@waitCond, Process.SecondsToTicks[parm.value]]; BROADCAST waitCond; }; ForceSample: Buttons.ButtonProc = { 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; lastVMPoll _ System.gmtEpoch; BROADCAST waitCond; }; Watcher: PROC = TRUSTED { ENABLE ABORTED => GO TO done; defaultInterval: INT _ UserProfile.Number["Watch.GCInterval", 16000]; defaultSample: INT _ UserProfile.Number["Watch.SamplePause", 2]; graphClass: ViewerClasses.ViewerClass = NEW[ViewerClasses.ViewerClassRec _ [paint: GraphPaint, destroy: MyDestroy]]; container, graph: ViewerClasses.Viewer _ NIL; wordsLabel, faultLabel, diskReqLabel, mdsLabel, gfiLabel, diskLabel: Labels.Label _ NIL; freeVMLabel, maxFreeLabel: Labels.Label _ NIL; cedarVMLabel, inZonesLabel: Labels.Label _ NIL; mappedPagesLabel,unmappedPagesLabel: Labels.Label _ NIL; unmappedFullLabel,unmappedPartLabel,unmappedMDSLabel: Labels.Label _ NIL; anonPagesLabel,toBootLabel,spareLabel: Labels.Label _ NIL; usedNormalLabel,finalizeNormalLabel: Labels.Label _ NIL; pinnedNormalLabel,freeNormalLabel: Labels.Label _ NIL; usedOverflowLabel,chainsOverflowLabel,freeOverflowLabel: Labels.Label _ NIL; gapX: INTEGER = 2; gapY: INTEGER = 2; lastX: INTEGER; nextX: INTEGER _ gapX; nextY: INTEGER _ 0; faults, faultRate, oldFaultRate, deltaFaults: INT _ 0; words, wordsRate, oldWordsRate, deltaWords: INT _ 0; maxIdleRate: INT _ 1; oldIdleRate, idleRate, lastIdle: INT _ 0; delta, mark, deltaMillis: ShowTime.Microseconds _ 0; vmWanted: BOOL _ TRUE; -- whether to sample VM stats this time -- newMDS, oldMDS: INT _ 0; newGFI, oldGFI: INT _ 0; unknownPages: INT _ 0; newDiskReq: INT _ 0; newDisk: INT _ 0; freeVM: INT _ 0; maxFreeVM: INT _ 0; cedarVM: INT _ 0; inZones: INT _ 0; mappedPages: INT _ 0; unmappedPages: INT _ 0; unmappedFullPages: INT _ 0; unmappedPartPages: INT _ 0; anonPages: INT _ 0; toBoot: INT _ 0; usedNormal: INTEGER _ 0; finalizeNormal: INTEGER _ 0; pinnedNormal: INTEGER _ 0; freeNormal: INTEGER _ 0; usedOverflow: INTEGER _ 0; chainsOverflow: INTEGER _ 0; freeOverflow: INTEGER _ 0; AddLabel: PROC [prefix: ROPE, chars: NAT _ 0, lastInLine: BOOL _ FALSE, ww: INTEGER _ 0] RETURNS [label: Labels.Label] = TRUSTED { 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}; }; AddButton: PROC [buttonName: ROPE _ NIL, chars: NAT _ 0, parm: Parameter _ NIL, proc: Buttons.ButtonProc _ NIL, lastInLine: BOOL _ FALSE] RETURNS [button: Buttons.Button] = TRUSTED { 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}; }; CreateGraph: PROC RETURNS[viewer: ViewerClasses.Viewer] = TRUSTED { W2: PROC [r: ROPE] RETURNS [INTEGER] = TRUSTED INLINE {RETURN[VFonts.RopeWidth[r]/2]}; xTemp: CARDINAL; wordsLabel _ AddLabel["Words", 9, TRUE]; xTemp _ lastX; [] _ AddLabel["CPU Load ", 0, TRUE]; xTemp _ MAX[xTemp, lastX]; faultLabel _ AddLabel["Faults", 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: faultLabel.wy - (wordsLabel.wy + wordsLabel.wh), data: NEW[GraphDataRec _ [[fullScale: 5], [fullScale: -1], [fullScale: 2]]]], paint: FALSE]; xTemp _ viewer.ww/5; [] _ Labels.Create [info: [name: "1", parent: container, wx: viewer.wx - W2["1"], wy: wordsLabel.wy, border: FALSE], paint: FALSE]; [] _ Labels.Create [info: [name: "10", parent: container, wx: viewer.wx + xTemp - W2["10"], wy: wordsLabel.wy, border: FALSE], paint: FALSE]; [] _ Labels.Create [info: [name: "100", parent: container, wx: viewer.wx + 2*xTemp - W2["100"], wy: wordsLabel.wy, border: FALSE], paint: FALSE]; [] _ Labels.Create [info: [name: "1000", parent: container, wx: viewer.wx + 3*xTemp - W2["1000"], wy: wordsLabel.wy, border: FALSE], paint: FALSE]; [] _ Labels.Create [info: [name: "10000", parent: container, wx: viewer.wx + 4*xTemp - W2["10000"], wy: wordsLabel.wy, border: FALSE], paint: FALSE]; xTemp _ viewer.ww/4; [] _ Labels.Create [info: [name: "1", parent: container, wx: viewer.wx - W2["1"], wy: faultLabel.wy, border: FALSE], paint: FALSE]; [] _ Labels.Create [info: [name: "3", parent: container, wx: viewer.wx + xTemp - W2["3"], wy: faultLabel.wy, border: FALSE], paint: FALSE]; [] _ Labels.Create [info: [name: "10", parent: container, wx: viewer.wx + xTemp*2 - W2["10"], wy: faultLabel.wy, border: FALSE], paint: FALSE]; [] _ Labels.Create [info: [name: "30", parent: container, wx: viewer.wx + xTemp*3 - W2["30"], wy: faultLabel.wy, border: FALSE], paint: FALSE]; }; --CreateGraph-- pauseParm: Parameter _ NEW[ParameterBlock _ [SetPause, NIL, defaultSample, 0, 1, 256]]; gcParm: Parameter _ NEW[ParameterBlock _ [SetGCInt, NIL, defaultInterval, 0, 1000, 1024000]]; WHILE NOT Runtime.IsBound[NumberLabels.CreateNumber] DO Process.Pause[Process.SecondsToTicks[1]]; ENDLOOP; container _ Containers.Create[ info: [name: "Watch", iconic: TRUE, column: right, scrollable: FALSE]]; ViewerOps.RegisterViewerClass[$BarGraph, graphClass]; graph _ CreateGraph[]; diskReqLabel _ AddLabel["requests", 7]; diskLabel _ AddLabel["disk", 5]; gfiLabel _ AddLabel["gfi", 3]; mdsLabel _ AddLabel["mds", 2]; freeVMLabel _ AddLabel["VM", 4]; maxFreeLabel _ AddLabel["VM run", 4, TRUE]; [] _ AddButton[buttonName: "GC interval", chars: 7, parm: gcParm]; SetLabel[gcParm.label, gcParm.value]; SafeStorage.TrimAllZones[]; SetGCInt[gcParm]; [] _ AddButton[buttonName: "GC", proc: CauseGCHit]; { gcStatusLabel: Labels.Label = AddLabel ["inactive.", 0, TRUE, ViewerSpecs.openRightWidth]; Containers.ChildXBound[container, gcStatusLabel]; Process.Detach[FORK CollectorWatcher[gcStatusLabel]]; }; [] _ AddButton[buttonName: "Sample", proc: ForceSample]; [] _ AddButton[buttonName: "interval", chars: 3, parm: pauseParm]; SetLabel[pauseParm.label, pauseParm.value]; SetPause[pauseParm]; cifsLabel _ AddLabel["CIFS status (inverted iff active)", 0, TRUE, ViewerSpecs.openRightWidth]; Containers.ChildXBound[container, cifsLabel]; CIFSFeedback.Register[UpdateCifsStatus]; Process.Detach[FORK DisplayCifsStatus]; newOpenHeight _ oldOpenHeight _ smallerOpenHeight _ nextY + gapY - 1; ViewerOps.SetOpenHeight[container, oldOpenHeight]; mappedPagesLabel _ AddLabel["mapped", 5]; toBootLabel _ AddLabel["boot", 4]; anonPagesLabel _ AddLabel["anon", 4]; cedarVMLabel _ AddLabel["Cedar", 4]; inZonesLabel _ AddLabel["zones", 4, TRUE]; unmappedPagesLabel _ AddLabel["unmapped", 4]; unmappedFullLabel _ AddLabel["full", 4]; unmappedPartLabel _ AddLabel["part", 4]; unmappedMDSLabel _ AddLabel["mds", 2, TRUE]; usedNormalLabel _ AddLabel["normalRC", 5]; finalizeNormalLabel _ AddLabel["finalize", 4]; pinnedNormalLabel _ AddLabel["pinned", 4]; freeNormalLabel _ AddLabel["free", 5, TRUE]; usedOverflowLabel _ AddLabel["overflowRC", 5]; chainsOverflowLabel _ AddLabel["chains", 4]; freeOverflowLabel _ AddLabel["free", 5, TRUE]; biggerOpenHeight _ nextY + gapY - 1; mark _ ShowTime.GetMark[]; idleCount _ 0; Process.Detach[FORK IdleProcess[]]; RTProcess.StartWatchingFaults[]; faults _ RTProcess.GetTotalPageFaults[]; words _ SafeStorage.NWordsAllocated[]; WHILE NOT quit AND NOT container.destroyed DO delta _ ShowTime.GetMark[] - mark; deltaMillis _ (delta + 500) / 1000; IF deltaMillis = 0 THEN LOOP; mark _ mark + delta; idleRate _ (idleCount-lastIdle) / deltaMillis; lastIdle _ idleCount; IF idleRate > maxIdleRate THEN { IF deltaMillis > 100 THEN maxIdleRate _ idleRate ELSE idleRate _ maxIdleRate }; deltaFaults _ RTProcess.GetTotalPageFaults[] - faults; faults _ faults + deltaFaults; faultRate _ (deltaFaults * 1000 + 500) / deltaMillis; deltaWords _ SafeStorage.NWordsAllocated[] - words; words _ words + deltaWords; wordsRate _ (deltaWords * 1000 + 500) / deltaMillis; IF container.iconic THEN { Process.Pause[Process.SecondsToTicks[1]]; LOOP; }; newGFI _ CountGFI[]; newDiskReq _ DiskDriverSharedImpl.totalNumberOfRequests; newDisk _ CountDisk[]; IF vmWanted AND NOT container.iconic THEN { [totalFree: freeVM, largestFree: maxFreeVM, mdsFree: newMDS, cedarPages: cedarVM, mappedPages: mappedPages, unmappedPages: unmappedFullPages, anonPages: anonPages, unknownPages: unknownPages, sparePages: unmappedPartPages, toBoot: toBoot] _ CountVM[Space.virtualMemory]; inZones _ SumZones[]; IF newOpenHeight > smallerOpenHeight THEN [usedNormal, pinnedNormal, finalizeNormal, chainsOverflow, freeNormal, usedOverflow, freeOverflow] _ GetRCTabStats[]; lastVMPoll _ System.GetGreenwichMeanTime[]; }; IF newOpenHeight # oldOpenHeight THEN { ViewerOps.SetOpenHeight[container, oldOpenHeight _ newOpenHeight]; IF NOT container.iconic THEN ViewerOps.ComputeColumn[ViewerOps.ViewerColumn[container]]; }; IF quit OR container.destroyed THEN RETURN; SetLabel[diskReqLabel, newDiskReq]; SetLabel[diskLabel, watchStats.diskFree _ newDisk]; SetLabel[mdsLabel, watchStats.mdsFree _ newMDS]; SetLabel[unmappedMDSLabel, newMDS]; SetLabel[gfiLabel, watchStats.gfiFree _ oldGFI _ newGFI]; SetLabel[wordsLabel, words]; SetLabel[faultLabel, faults]; IF oldFaultRate # faultRate OR oldWordsRate # wordsRate OR oldIdleRate # idleRate THEN GraphSet[ graph, oldWordsRate _ wordsRate, watchStats.cpuLoad _ 1 - (oldIdleRate _ idleRate)/(maxIdleRate*1.0), oldFaultRate _ faultRate]; SetLabel[freeVMLabel, watchStats.vmFree _ freeVM]; SetLabel[maxFreeLabel, watchStats.vmRun _ maxFreeVM]; SetLabel[inZonesLabel, watchStats.vmZones _ inZones]; SetLabel[cedarVMLabel, watchStats.vmCedar _ cedarVM]; SetLabel[mappedPagesLabel, mappedPages]; SetLabel[unmappedFullLabel, unmappedFullPages]; SetLabel[unmappedPartLabel, unmappedPartPages]; unmappedPages _ unmappedFullPages + unmappedPartPages + newMDS; SetLabel[unmappedPagesLabel, unmappedPages]; SetLabel[anonPagesLabel, anonPages]; SetLabel[toBootLabel, toBoot]; SetLabel[usedNormalLabel, usedNormal]; SetLabel[finalizeNormalLabel, finalizeNormal]; SetLabel[pinnedNormalLabel, pinnedNormal]; SetLabel[freeNormalLabel, freeNormal]; SetLabel[usedOverflowLabel, usedOverflow]; SetLabel[chainsOverflowLabel, chainsOverflow]; SetLabel[freeOverflowLabel, freeOverflow]; vmWanted _ WaitForUpdate[]; ENDLOOP; quit _ TRUE; RTProcess.StopWatchingFaults[]; EXITS done => {RTProcess.StopWatchingFaults[]}; }; TRUSTED { Process.Detach[FORK Watcher]; }; END. PWatch.mesa; written by S. McGregor McGregor, August 8, 1983 12:31 pm Andrew Birrell, November 12, 1982 10:07 am Russ Atkinson, May 4, 1983 9:26 pm Paul Rovner, March 1, 1983 5:21 pm Last Edited by: Pausch, August 12, 1983 5:51 pm **** Useful types from other interfaces **** **** Useful local types and constants **** **** The following stats can be examined by the interpreter **** **** 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] Find out if it is for the Cedar VM file. We believe that the boot file is the first we see. we just passed over a free run of VM pages the last run of VM pages was free At this point we have a leaf space, so add it to the counts. [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] build enclosing viewer line 0: bar graphs line 1: mds, gfi, disk, freeVM line 2: gc interval, button & status line 3: sample button and CIFS status set an aesthetically sized open window line 4: other mapped VM stuff line 5: unmapped VM stuff line 6: RC table stats line 7: RC table stats initialize measurments Get the time delta Update the idle rate data Update the fault data Update the alloc data The user really does not want to see the data now. We performed the above calculations just so we don't overflow various numbers if the user keeps this tool iconic for a long time. Sample the new numbers Calculate the stats for the VM usage (but only if they will be displayed) We also try to get RC table stats here, but only if they will be seen! Update the display (only where necessary) Lastly, wait for the pause interval **** Initialization code **** Ê ¸˜šœ"™"Jšœ!™!Jšœ*™*Jšœ"™"Jšœ"™"—J™/šÏk ˜ Jšœœ0˜=Jšœ œ ˜Jšœ œ˜'Jšœœ˜Jšœœ˜3Jšœœ.˜8Jšœœ-˜9Jšœœ(˜4Jšœ œ1˜CJšœœœ ˜&Jšœœ;˜HJšœœ ˜Jšœœ&œ˜6Jšœ œ?˜NJšœ œ˜!šœ ˜Jšœ‹˜‹—Jšœœ)˜6Jšœœ ˜šœ ˜Jšœ ˜ —Jšœœœ˜Jšœ œ˜(šœ˜ Jšœp˜p—Jšœœ5˜AJšœ œ ˜Jšœœ@˜Sšœ ˜Jšœ^˜^—Jšœ œ˜#šœœ˜'J˜——Jšœœ˜˜š˜Jšœö˜ö—š˜Jšœ˜—J˜Jšœ˜—J˜šœ,™,Jšœœ˜Jšœœ˜Jšœ œ˜-Jšœœœ˜—J˜šœ*™*Jšœ œ˜-Jšœœ˜4Jšœ œE˜RJšœ œœ˜$šœ œœ˜JšœœÏc˜-J˜!Jšœ œž2˜D—Jšœ œ˜,Jšœœœ$˜@Jšœ œœ˜%šœœœ˜Jšœœ ˜Jšœ˜Jšœœ˜——J˜šœ@™@šœ œœ˜Jšœœ˜$Jšœœ˜Jšœœ˜Jšœ œ˜Jšœ œ˜J˜—J˜—J˜šœ$™$Jšœœ˜Jš œ œœœœ˜%Jšœ œ(˜Jšœœœœœœ œ˜>Jšœœœœ˜)J˜6J˜J˜—šŸ œœœ œœœœœ˜UJšœœ4œ˜EJšœ œœ˜Jšœ˜!J˜J˜—šŸœœœ˜#šœ˜ Jšœœ˜šœ˜Jšœ1˜5šœ˜Jšœœ"˜*šœ œ˜Jšœ=˜=—J˜J˜1Jšœ˜——Jšœ˜—Jšœ œ˜Jšœœ˜J˜J˜—šŸ œœœ˜J˜Jšœœœœ˜5J˜J˜—š Ÿœœœ œœ˜:Jšœ&˜&šœœœ˜ Jšœ˜Jšœœœ˜"Jšœ-˜-Jšœ˜—J˜J˜—š Ÿœœœœœ˜FJšœA˜GJ˜J˜—š Ÿœœœœœœ˜PJšœœœœœœœ˜3Jšœ9˜9Jšœ,˜,Jšœ˜%Jšœ œœ œ˜CJ˜J˜—šŸœœœ˜.Jšœžœ˜Jšœžœ˜Jšœžœ˜Jšœžœ˜Jšœ žœ˜"Jšœ žœ˜ Jšœžœ˜Jšœ žœ˜!Jšœ žœ˜$Jšœ ž&œ˜1Jšœ ž%œ˜1Jšœ ž-œ˜;Jšœ ž*œ˜4Jšœ ž7œ˜DJšœ ž(˜2Jšœžœœ˜-šœ˜Jš œœœœœ˜MJ˜Jšœ'œ˜0Jšœœ˜ šœD˜DJšœ˜—Jšœ˜Jšœ˜šœ œœœ˜&Jšœ(™(Jšœ,˜,Jšœ"˜"šœœœœœœœœ ˜6Jšœ#˜#š˜Jšœ.˜.Jšœ œœ˜—J˜—šœœ˜šœ˜Jšœ ˜ —šœ&˜&Jšœ˜—šœ,˜,Jšœ2™2Jšœ˜Jšœ˜—šœ˜Jšœ˜—Jšœ˜—J˜—šœœœœ˜1Jšœ œ˜J˜Jšœœ˜Jšœ!œœœ ˜9šœ˜!šœ5˜5Jšœœœœ˜8—šœœ˜Jšœ*™*Jšœœ˜ šœ ˜ šœ˜šœ˜Jšœ˜Jšœ˜#——šœ˜Jšœ˜Jšœ˜Jšœœ˜/——Jšœ˜—Jšœ˜Jšœ(œœ ˜8Jšœ˜Jšœ˜—Jšœ ˜ šœœ˜Jšœ!™!Jšœœ˜ šœ ˜ š˜šœ˜Jšœ˜Jšœ˜#——šœ˜Jšœ˜Jšœ˜Jšœœ˜/——Jšœ˜—Jšœ˜š˜Jšœ˜šœ˜Jšœ<˜šœœœ˜&Jšœ#˜#Jšœœ˜(Jšœœœ˜Išœ œ˜˜ Jšœ˜—˜ Jšœ˜šœ˜Jšœ+˜/Jšœ5˜5Jšœ˜—J˜—Jšœ˜—Jšœ˜—šœœœ ˜Jšœ&˜&Jšœ"˜"Jšœœ˜.šœœ˜˜ Jšœ ˜ —˜ Jšœ˜šœ˜Jšœ+˜/Jšœ5˜5Jšœ˜—J˜—Jšœ˜—Jšœ˜—Jšœ˜—šŸ œœœœ˜'J˜@J˜J˜—šœ!œ˜*JšœN™Nšœœœ˜Jšœœ ˜.J˜.Jšœ&œ˜?Jšœœ˜)Jšœ œ!˜0Jšœ œœ˜,J˜/J˜—J˜J˜—šŸœœ"˜8Jšœœœœ˜Jšœœ ˜Jšœ œ˜(Jšœ œ ˜Jšœœ˜Jšœœ/œ˜RJšœœ0˜Fšœœœ˜šœ œœœ˜8˜SJ˜@——J˜"Jšœ œœ˜Jšœ œ˜Jšœœ ˜J˜2Jšœœœ˜Jšœ<˜C˜SJ˜#—J˜+Jšœœœ˜šœ˜Jšœ<˜?Jšœ˜šœ'œ˜0Jšœ$˜$J˜Jšœ˜Jšœ ˜ Jšœ˜——šœ˜Jšœœœ˜šœ ˜ Jšœ˜šœ ˜ J˜J˜J˜Jšœœ˜+Jšœœ˜+——J˜—Jšœ˜—š˜J˜ —J˜J˜—šŸœœœ˜*Jšœœœ˜J˜3J˜J˜—šœ œ˜.Jšœœœ˜Jšœœ˜,Jšœœ˜ Jš œ ˜J˜J˜—š Ÿ œœœœ œ˜7Jšœœœ˜J˜Jšœ œž ˜#Jšœ œ˜Jšœ ˜J˜$J˜J˜!J˜3˜J˜——šŸœœœœ˜2Jšœœœ˜J˜BJš œ ˜J˜J˜—šœ#˜#JšœN™NJšœœœ˜Jšœœ ˜.Jšœ œ#˜2Jšœœ"˜/Jšœ˜J˜J˜—šŸœœœ˜ JšœN™NJšœœœ˜J˜Jš œ ˜J˜J˜—šŸœœœ˜Jšœœœœ˜Jšœœ1˜EJšœœ.˜@˜'JšœI˜L—Jšœ)œ˜-JšœTœ˜XJšœ*œ˜.Jšœ+œ˜/Jšœ4œ˜8JšœEœ˜IJšœ6œ˜:Jšœ4œ˜8Jšœ2œ˜6JšœHœ˜LJšœœ˜Jšœœ˜Jšœœ˜Jšœœ˜Jšœœ˜Jšœ.œ˜6Jšœ,œ˜4Jšœ œ˜Jšœ!œ˜)J˜4Jšœ œœž*˜AJšœœ˜Jšœœ˜Jšœœ˜J˜Jšœ œ˜Jšœ œ˜Jšœœ˜Jšœ œ˜Jšœ œ˜Jšœ œ˜Jšœ œ˜Jšœœ˜Jšœœ˜Jšœœ˜Jšœ œ˜Jšœœ˜Jšœ œ˜Jšœœ˜Jšœœ˜Jšœ œ˜Jšœœ˜Jšœœ˜Jšœœ˜J˜šŸœ˜Jš œ œ œœœœ˜IJšœœ˜)šœ ˜ šœ˜šœ˜šœ˜Jšœ)œ ˜N—Jšœœ˜—J˜šœ"˜"Jšœ œœ˜JJšœ ˜ Jšœœ˜—J˜—š˜˜šœ˜Jšœ)œ ˜N—Jšœœ˜———J˜+šœ œ˜J˜2—J˜—šŸ œ˜Jšœ œœ œœœœœ˜yJšœœ˜,Jšœœ˜"Jšœœœ˜*˜JšœJœ˜PJšœœ'œ˜8—J˜šœœ˜šœ ˜ šœ˜šœ˜Jšœ œœ˜JJšœ ˜ Jšœœ˜———šœœ˜ šœ˜šœ˜Jšœ œœ˜JJšœœ˜———Jšœ˜—šœ œœ˜J˜Jšœœœ˜&J˜—J˜šœ œ˜J˜ J˜&—J˜—šŸ œœœ!œ˜Cš Ÿœœœœœœ˜5Jšœœ˜ —Jšœœ˜Jšœ"œ˜7Jšœœ œ˜?Jšœ#œ œ˜D˜ J˜˜%J˜OJ˜4JšœœE˜N—Jšœœ˜—J˜˜J˜>Jšœœ œ˜2—˜J˜HJšœœ œ˜2—˜J˜LJšœœ œ˜2—˜˜OJšœœ œ˜2——˜˜QJšœœ œ˜2——J˜˜J˜>Jšœœ œ˜2—˜˜GJšœœ œ˜2——˜J˜JJšœœ œ˜2—˜J˜JJšœœ œ˜2—Jšœž˜J˜—˜Jšœœ˜@—˜Jšœœ&˜IJ˜—šœœ,˜7J˜)Jšœ˜J˜—Jšœ™˜Jšœœœ˜HJ˜—Jšœ™Jšœ5˜5J˜J˜Jšœ™J˜'J˜ J˜J˜J˜ Jšœ%œ˜+J˜Jšœ$™$JšœB˜BJ˜%Jšœ˜J˜Jšœ3˜3šœ˜˜&Jšœœ˜3—J˜1Jšœœ"˜5Jšœ˜—J˜Jšœ%™%J˜8JšœB˜BJ˜+J˜˜ Jšœ1œ˜S—J˜-J˜(Jšœœ˜'J˜Jšœ&™&JšœE˜EJšœ3˜3J˜Jšœ™Jšœ)˜)Jšœ"˜"Jšœ%˜%Jšœ$˜$Jšœ$œ˜*J˜Jšœ™Jšœ-˜-Jšœ(˜(Jšœ(˜(Jšœ&œ˜,J˜Jšœ™Jšœ*˜*Jšœ.˜.Jšœ*˜*Jšœ&œ˜,J˜Jšœ™Jšœ.˜.Jšœ,˜,Jšœ(œ˜.J˜Jšœ$˜$J˜Jšœ™J˜J˜Jšœœ˜#J˜ J˜(J˜&J˜š œœœœ˜-Jšœ™Jšœ"˜"J˜#Jšœœœ˜J˜J˜Jšœ!™!J˜.J˜šœ˜Jšœœœœ˜QJ˜—Jšœ™J˜6J˜J˜5J˜Jšœ™J˜3J˜J˜4J˜šœœ˜Jšœµ™µJ˜)Jšœ˜Jšœ˜J˜—Jšœ™J˜J˜8J˜šœ œœ˜+JšœI™IJšœŽ˜ŽJšœ˜JšœF™Fšœ#˜)Jšœu˜u—Jšœ+˜+J˜—J˜šœœ˜'JšœB˜Bšœœ˜Jšœ;˜;—J˜—Jšœ)™)Jšœœœœ˜+J˜#Jšœ3˜3Jšœ0˜0Jšœ#˜#Jšœ9˜9J˜J˜šœ˜Jšœ˜Jšœ˜˜ J˜J˜JšœD˜DJ˜——Jšœ2˜2Jšœ5˜5Jšœ5˜5Jšœ5˜5Jšœ(˜(Jšœ/˜/Jšœ/˜/Jšœ?˜?Jšœ,˜,Jšœ$˜$Jšœ˜Jšœ&˜&Jšœ.˜.Jšœ*˜*Jšœ&˜&Jšœ*˜*Jšœ.˜.Jšœ*˜*J˜Jšœ#™#J˜J˜Jšœ˜J˜—Jšœœ˜ J˜J˜š˜J˜)—J˜——J˜šœ™šœ˜ Jšœœ ˜Jšœ˜——J˜šœ˜J˜J˜J˜——…—oZ˜b