DIRECTORY BasicTime USING [GetClockPulses, Pulses, PulsesToSeconds], Commander USING [CommandProc, Register], CommBuffer USING [], Containers USING [Container, Create], Convert USING [AppendF, AppendInt, AppendRope, RopeFromInt], CedarProcess USING [SetPriority], CommDriver USING [CreateInterceptor, DestroyInterceptor, GetNetworkChain, Interceptor, Network, RecvInterceptor], CommDriverType USING [Encapsulation], Imager USING [black, Context, DoSave, MaskRectangle, SetColor, SetFont, SetXY, SetXYI, ShowRope, ShowText, TranslateT, white], ImagerFont USING [Find, Font, RopeWidth, TextWidth], Process USING [Pause, MsecToTicks], Real USING [Round, RoundI], RealFns USING [Log], RefText USING [ObtainScratch, ReleaseScratch], Pup USING [allHosts, nullSocket], PupName USING [AddressToRope], Rope USING [ROPE], ViewerClasses USING [PaintProc, Viewer, ViewerClass, ViewerClassRec], ViewerEvents USING [EventProc, RegisterEventProc], ViewerForkers USING [ForkPaint], ViewerOps USING [CreateViewer, OpenIcon, RegisterViewerClass, SetOpenHeight], XNS USING [broadcastHost]; EtherLoad: CEDAR MONITOR LOCKS data USING data: Data IMPORTS BasicTime, CedarProcess, CommDriver, Commander, Containers, Convert, Imager, ImagerFont, Process, PupName, Real, RealFns, RefText, ViewerEvents, ViewerForkers, ViewerOps EXPORTS CommBuffer = { Encapsulation: PUBLIC TYPE = CommDriverType.Encapsulation; Context: TYPE ~ Imager.Context; Font: TYPE ~ ImagerFont.Font; ROPE: TYPE ~ Rope.ROPE; Data: TYPE = REF DataRecord; DataRecord: TYPE = MONITORED RECORD [ container: Containers.Container _ NIL, graphs: ViewerClasses.Viewer _ NIL, bits: REF ArrayOfPointsRep _ NEW [ArrayOfPointsRep _ ALL[0]], bcPackets: REF ArrayOfPointsRep _ NEW [ArrayOfPointsRep _ ALL[0]], pups: REF ArrayOfPointsRep _ NEW [ArrayOfPointsRep _ ALL[0]], xns: REF ArrayOfPointsRep _ NEW [ArrayOfPointsRep _ ALL[0]], arpa: REF ArrayOfPointsRep _ NEW [ArrayOfPointsRep _ ALL[0]], helvetica8: Font _ ImagerFont.Find["Xerox/TiogaFonts/Helvetica8"], helvetica10: Font _ ImagerFont.Find["Xerox/TiogaFonts/Helvetica10"], lastww: NAT _ 0, nextAdd: NAT _ 0, nextPlot: NAT _ 0, width: NAT _ maxWidth, lostPlots: BOOL _ FALSE, watcher: PROCESS _ NIL ]; leading: NAT = 2; separation: NAT = 10; buttonHeight: NAT = 15; headerHeight: NAT = 12; left: NAT = 72; right: NAT = 40; bottom: NAT = leading+headerHeight+leading; top: NAT = 10; bigHeight: NAT = 12*bigLogScale; smallHeight: NAT = 10*smallLogScale; smallerHeight: NAT = 5*smallLogScale; numberX: NAT _ 0; numberY: NAT _ 32; numberW: NAT _ 32; numberH: NAT _ 16; titleX: INTEGER _ 4; titleY: INTEGER _ 10; subTitleX: INTEGER _ 10; subTitleY: INTEGER _ -2; maxWidth: NAT = 60*12; -- It is nice to keep this an integral # of minutes totalHeight: NAT = 1*(top+bigHeight+bottom) + 1*(top+smallerHeight+bottom) + 3*(top+smallHeight+bottom); bigLogScale: NAT = 20; -- pixels per factor of 2 smallLogScale: NAT = 10; ArrayOfPoints: TYPE = REF ArrayOfPointsRep; ArrayOfPointsRep: TYPE = ARRAY [0..maxWidth) OF REAL; Title: PROC [context: Context, data: Data, t1, t2: ROPE] ~ { Imager.SetFont[context, data.helvetica10]; Imager.SetXYI[context, -left+titleX, titleY]; Imager.ShowRope[context, t1]; Imager.SetFont[context, data.helvetica8]; Imager.SetXYI[context, -left+subTitleX, subTitleY]; Imager.ShowRope[context, t2]; }; BasicFrame: PROC [context: Context, data: Data, height: NAT] ~ { Imager.SetFont[context, data.helvetica8]; Imager.SetColor[context, Imager.white]; Imager.MaskRectangle[context, [x: 0, y: 0, w: data.width, h: height]]; Imager.SetColor[context, Imager.black]; Imager.MaskRectangle[context, [x: -1, y: 0, w: data.width+2, h: -2.0]]; Imager.MaskRectangle[context, [x: 0, y: -1, w: -1.0, h: height+1]]; Imager.MaskRectangle[context, [x: data.width, y: -1, w: 1.0, h: height+1]]; FOR i: INT _ 0, i + 60 UNTIL i > data.width DO Imager.MaskRectangle[context, [x: i, y: -1, w: -1.0, h: -5.0]]; ENDLOOP; }; Frame: PROC [context: Context, data: Data, height, max: INT] ~ { BasicFrame[context, data, height]; FOR i: INT _ 0, i + 25 UNTIL i > height DO value: INT = max*i/height; tag: ROPE = Convert.RopeFromInt[value]; ropeWidth: REAL = ImagerFont.RopeWidth[data.helvetica8, tag].x; Imager.SetXY[context, [0-ropeWidth-5-5, i]]; IF i # 0 THEN Imager.ShowRope[context, tag]; Imager.MaskRectangle[context, [x: -1, y: i, w: -5.0, h: -1.0]]; Imager.MaskRectangle[context, [x: data.width+1, y: i, w: +5.0, h: -1.0]]; Imager.SetXYI[context, 0+data.width+5+5, i]; Imager.ShowRope[context, tag]; ENDLOOP; }; FrameLog: PROC [context: Context, data: Data, height, logScale, max: NAT] ~ { value: INT _ max; BasicFrame[context, data, height]; FOR i: INT _ height, i - logScale UNTIL i < 0 DO tag: ROPE = Convert.RopeFromInt[value]; ropeWidth: REAL = ImagerFont.RopeWidth[data.helvetica8, tag].x; Imager.SetXY[context, [0-ropeWidth-5-5, i]]; IF i # 0 THEN Imager.ShowRope[context, tag]; Imager.MaskRectangle[context, [x: -1, y: i, w: -5.0, h: -1.0]]; Imager.MaskRectangle[context, [x: data.width+1, y: i, w: +5.0, h: -1.0]]; Imager.SetXYI[context, 0+data.width+5+5, i]; Imager.ShowRope[context, tag]; value _ value/2; ENDLOOP; }; InitData: PROC [context: Context, data: Data, height, max: INT, t1, t2: ROPE] = { Imager.TranslateT[context, [0, -(height + top)]]; Frame[context, data, height, max]; Title[context, data, t1, t2]; Imager.TranslateT[context, [0, -bottom]]; }; InitLog: PROC [context: Context, data: Data, height, logScale, max: NAT, t1, t2: ROPE] = { Imager.TranslateT[context, [0, -(height + top)]]; FrameLog[context, data, height, logScale, max]; Title[context, data, t1, t2]; Imager.TranslateT[context, [0, -bottom]]; }; PlotThickens: PROC [context: Context, data: Data, sample: REAL, i, base, height: NAT, update: BOOL] = { IF update THEN { quarter: NAT _ height/4; half: NAT _ quarter+quarter; tick: NAT _ (i + 2) MOD data.width; next: NAT _ (i + 10) MOD data.width; Imager.SetColor[context, Imager.white]; Imager.MaskRectangle[context, [x: next, y: base, w: 1.0, h: height]]; Imager.MaskRectangle[context, [x: i, y: base, w: 1.0, h: height]]; Imager.MaskRectangle[context, [x: numberX-left, y: base+numberY, w: numberW, h: numberH]]; Imager.SetColor[context, Imager.black]; Imager.MaskRectangle[context, [x: tick, y: base+quarter-2, w: 1.0, h: 4]]; -- tick marks Imager.MaskRectangle[context, [x: tick, y: base+half-2, w: 1.0, h: 4]]; Imager.MaskRectangle[context, [x: tick, y: base+quarter+half-2, w: 1.0, h: 4]]; }; IF sample > 0 THEN { h: NAT _ Real.Round[sample]; Imager.MaskRectangle[context, [x: i, y: base, w: 1.0, h: MAX[h, 1]]]; }; }; ShowNumber: PROC [context: Context, data: Data, sample: REAL, base: NAT, point: BOOL] = { text: REF TEXT _ RefText.ObtainScratch[16]; SELECT TRUE FROM sample = 0 => text _ Convert.AppendRope[text, "0", FALSE]; point AND sample < 0.05 => text _ Convert.AppendRope[text, "< 0.1", FALSE]; point AND sample < 9.95 => text _ Convert.AppendF[text, sample, 1]; sample < 0.5 => text _ Convert.AppendRope[text, "< 1", FALSE]; ENDCASE => text _ Convert.AppendInt[text, Real.Round[sample]]; Imager.SetXYI[context, numberX-left+(numberW-Real.RoundI[ImagerFont.TextWidth[data.helvetica8, text].x]), base+numberY]; Imager.ShowText[context, text]; RefText.ReleaseScratch[text]; }; ResetPlotted: ENTRY PROC [data: Data, newWidth: NAT] = { nextPlot: NAT _ data.nextAdd+10; IF nextPlot >= data.width THEN nextPlot _ nextPlot-data.width; data.nextPlot _ nextPlot; data.lostPlots _ FALSE; IF newWidth # data.width THEN { data.nextAdd _ data.nextPlot _ 0; data.width _ newWidth; data.bits^ _ ALL[0.0]; data.bcPackets^ _ ALL[0.0]; data.pups^ _ ALL[0.0]; data.xns^ _ ALL[0.0]; data.arpa^ _ ALL[0.0]; }; }; GetSample: ENTRY PROC [data: Data] RETURNS [index: INTEGER, bits, bcPackets, pups, xns, arpa: REAL] = { nextAdd: NAT _ data.nextAdd; nextPlot: NAT _ data.nextPlot; index _ -1; IF data.lostPlots OR nextAdd # nextPlot THEN { next: NAT _ (index _ nextPlot)+1; IF next = data.width THEN next _ 0; data.nextPlot _ next; bits _ data.bits[index]; bcPackets _ data.bcPackets[index]; pups _ data.pups[index]; xns _ data.xns[index]; arpa _ data.arpa[index]; data.lostPlots _ FALSE; }; }; AddSample: ENTRY PROC [data: Data, bits, bcPackets, pups, xns, arpa: REAL] = { index: NAT _ data.nextAdd; next: NAT _ index+1; IF next = data.width THEN next _ 0; IF next = data.nextPlot THEN data.lostPlots _ TRUE; data.nextAdd _ next; data.bits[index] _ bits; data.bcPackets[index] _ bcPackets; data.pups[index] _ pups; data.xns[index] _ xns; data.arpa[index] _ arpa; }; Log: PROC [n: REAL, height, logScale: NAT] RETURNS [log: REAL] = { IF n <= 0 THEN RETURN[0]; log _ height + logScale*RealFns.Log[2, n]; IF log < 0 THEN log _ 0; }; AntiLog: PROC [ln: INT] RETURNS [n: INT _ 1] = { UNTIL ln = 0 DO ln _ ln - 1; n _ n+n; ENDLOOP; n _ n/2; }; Ether: TYPE = RECORD [ packets: LONG CARDINAL _ 0, bcPackets: LONG CARDINAL _ 0, bytes: LONG CARDINAL _ 0, pups: LONG CARDINAL _ 0, xns: LONG CARDINAL _ 0, arpa: LONG CARDINAL _ 0 ]; RecvProc: CommDriver.RecvInterceptor = { -- Should be MONITORed ether: REF Ether _ NARROW[data]; ether.packets _ ether.packets + 1; ether.bytes _ ether.bytes + bytes; IF recv = other AND network.type = ethernetOne THEN SELECT buffer.ovh.encap.ethernetOneType FROM fromImp, toImp => recv _ arpa; -- IMP Relay hack ENDCASE; SELECT recv FROM pup => ether.pups _ ether.pups+1; xns => ether.xns _ ether.xns+1; arpa => ether.arpa _ ether.arpa+1; ENDCASE => NULL; SELECT network.type FROM ethernet => IF buffer.ovh.encap.ethernetDest = XNS.broadcastHost THEN ether.bcPackets _ ether.bcPackets+1; ethernetOne => IF buffer.ovh.encap.ethernetOneDest = Pup.allHosts THEN ether.bcPackets _ ether.bcPackets+1; ENDCASE => NULL; }; milliSecondsPerPixel: INT _ 1000; network: CommDriver.Network _ CommDriver.GetNetworkChain[]; Collector: PROC [data: Data] = { viewer: ViewerClasses.Viewer _ data.container; start, stop: BasicTime.Pulses; sec: REAL; i: NAT _ 0; ether: REF Ether _ NEW[Ether]; oldEther: Ether _ ether^; interceptor: CommDriver.Interceptor; CedarProcess.SetPriority[excited]; interceptor _ CommDriver.CreateInterceptor[ network: network, sendMask: ALL[FALSE], sendProc: NIL, recvMask: ALL[TRUE], recvProc: RecvProc, data: ether, promiscuous: TRUE]; DO ENABLE ABORTED => EXIT; packets, bcPackets, bits, pups, xns, arpa: REAL _ 0.0; Process.Pause[Process.MsecToTicks[milliSecondsPerPixel]]; stop _ BasicTime.GetClockPulses[]; sec _ BasicTime.PulsesToSeconds[stop-start]; start _ stop; IF sec = 0 THEN LOOP; { temp: Ether _ ether^; packets _ REAL[temp.packets-oldEther.packets]/sec; bcPackets _ REAL[temp.bcPackets-oldEther.bcPackets]/sec; bits _ REAL[8*(temp.bytes-oldEther.bytes)]/sec; pups _ REAL[temp.pups-oldEther.pups]/sec; xns _ REAL[temp.xns-oldEther.xns]/sec; arpa _ REAL[temp.arpa-oldEther.arpa]/sec; oldEther _ temp; }; AddSample[data, bits, bcPackets, pups, xns, arpa]; IF viewer.destroyed THEN EXIT; IF NOT viewer.iconic THEN ViewerForkers.ForkPaint[viewer, client, FALSE, $Update, TRUE]; ENDLOOP; CommDriver.DestroyInterceptor[interceptor]; }; RepaintViewer: ViewerClasses.PaintProc = { data: Data = NARROW[self.data]; ww: NAT _ data.container.ww; update: BOOL _ whatChanged = $Update AND NOT data.lostPlots AND ww = data.lastww; Imager.TranslateT[context, [left, bottom]]; IF NOT update THEN { newWidth: INTEGER _ data.width; init: PROC = { label: ROPE = PupName.AddressToRope[ [[network.pup.net], [network.pup.host], Pup.nullSocket]]; Imager.TranslateT[context, [0, totalHeight-bottom]]; InitLog[context, data, bigHeight, bigLogScale, 3200, label, "Kbits/s"]; InitLog[context, data, smallerHeight, smallLogScale, 64, "Brdcst", "packets/s"]; InitLog[context, data, smallHeight, smallLogScale, 1024, "Pup", "packets/s"]; InitLog[context, data, smallHeight, smallLogScale, 1024, "XNS", "packets/s"]; InitLog[context, data, smallHeight, smallLogScale, 1024, "Arpa", "packets/s"]; }; IF ww # data.lastww THEN { newWidth _ MAX[60, MIN[maxWidth, ww - left - right]]; IF newWidth < 60 THEN newWidth _ 60; data.lastww _ ww; }; ResetPlotted[data, newWidth]; Imager.DoSave[context, init]; }; Imager.SetColor[context, Imager.black]; Imager.SetFont[context, data.helvetica8]; DO index: INTEGER; bits, bcPackets, pups, xns, arpa, sample: REAL; base: NAT _ totalHeight; [index, bits, bcPackets, pups, xns, arpa] _ GetSample[data]; IF index < 0 THEN RETURN; base _ base - (bottom+bigHeight+top); sample _ Log[MIN[bits/3200000, 1.0], bigHeight, bigLogScale]; PlotThickens[context, data, sample, index, base, bigHeight, update]; IF update THEN ShowNumber[context, data, bits*1e-3, base, TRUE]; base _ base - (bottom+smallerHeight+top); sample _ Log[MIN[bcPackets/64, 1.0], smallerHeight, smallLogScale]; PlotThickens[context, data, sample, index, base, smallerHeight, update]; IF update THEN ShowNumber[context, data, bcPackets, base, TRUE]; base _ base - (bottom+smallHeight+top); sample _ Log[MIN[pups/1024, 1.0], smallHeight, smallLogScale]; PlotThickens[context, data, sample, index, base, smallHeight, update]; IF update THEN ShowNumber[context, data, pups, base, TRUE]; base _ base - (bottom+smallHeight+top); sample _ Log[MIN[xns/1024, 1.0], smallHeight, smallLogScale]; PlotThickens[context, data, sample, index, base, smallHeight, update]; IF update THEN ShowNumber[context, data, xns, base, TRUE]; base _ base - (bottom+smallHeight+top); sample _ Log[MIN[arpa/1024, 1.0], smallHeight, smallLogScale]; PlotThickens[context, data, sample, index, base, smallHeight, update]; IF update THEN ShowNumber[context, data, arpa, base, TRUE]; ENDLOOP; }; global: Data _ NIL; Poof: ViewerEvents.EventProc = { data: Data = NARROW[viewer.data]; IF global = data THEN global _ NIL; }; MakeTool: Commander.CommandProc = { data: Data _ NEW [DataRecord]; data.container _ Containers.Create[ info: [ name: "Ether Load", iconic: TRUE, column: left, menu: NIL, scrollable: FALSE, data: data ], paint: FALSE]; ViewerOps.SetOpenHeight[data.container, totalHeight]; data.graphs _ ViewerOps.CreateViewer[ flavor: $EtherLoad, info: [ parent: data.container, wx: 0, wy: 0, ww: left+maxWidth+left, wh: totalHeight, scrollable: FALSE, border: FALSE, data: data], paint: FALSE]; [] _ ViewerEvents.RegisterEventProc[Poof, destroy, data.graphs, TRUE]; global _ data; data.watcher _ FORK Collector[data]; ViewerOps.OpenIcon[icon: data.container, closeOthers: TRUE]; }; graphClass: ViewerClasses.ViewerClass _ NEW[ViewerClasses.ViewerClassRec _ [paint: RepaintViewer]]; ViewerOps.RegisterViewerClass[$EtherLoad, graphClass]; Commander.Register["EtherLoad", MakeTool, "Make Tool for watching Ethernet load"]; }. ”EtherLoad.mesa Copyright (C) 1985, 1986 Xerox Corporation. All rights reserved. Hal Murray, June 3, 1986 11:08:19 pm PDT Russ Atkinson (RRA) January 31, 1986 7:21:43 pm PST Imager.MaskRectangle[context, [x: -left, y: 0, w: left+data.width+left, h: 1]]; Imager.MaskRectangle[context, [x: -left, y: 0, w: left+data.width+left, h: 1]]; Mask off the number region There is something worth plotting. Make sure that it gets at least one pixel. This forces us to paint fully, since we have wrapped around and are now losing plots. n IN [0..1.0] => log IN [0..height] [recv: CommDriver.RecvType, data: REF ANY, network: CommDriver.Network, buffer: CommDriver.Buffer, bytes: NAT] RETURNS [kill: BOOL _ FALSE] [self: ViewerClasses.Viewer, context: Imager.Context, whatChanged: REF ANY, clear: BOOL] RETURNS [quit: BOOL _ FALSE] We must paint in the Frames and stuff The window width has changed, so refigure the width Κ<˜codešœ™K™AK™(K™3K˜šΟk ˜ Kšœ œ+˜:Kšœ œ˜(Kšœ œ˜Kšœ œ˜%Kšœœ/˜˜MKšœœ˜——headšœ œ˜Kšœœ ˜Kšœͺ˜±šœ˜K˜—Kšœœœ ˜:K˜Kšœ œ˜Kšœœ˜Kšœœœ˜K˜Kšœœœ ˜šœ œœ˜%Kšœ"œ˜&Kšœœ˜#Kšœœœœ˜=Kšœ œœœ˜BKšœœœœ˜=Kšœœœœ˜Kšœ7˜>—Kšœx˜xKšœ˜Kšœ˜K˜K˜—šŸ œœœœ˜8Kšœ œ˜ Kšœœ ˜>Kšœ˜Kšœœ˜šœœ˜Kšœ!˜!Kšœ˜Kšœ œ˜Kšœœ˜Kšœ œ˜Kšœ œ˜Kšœ œ˜K˜—K˜K˜—š Ÿ œœœœ œ$œ˜gKšœ œ˜Kšœ œ˜Kšœ ˜ šœœœ˜.Kšœœ˜!Kšœœ ˜#Kšœ˜Kšœ˜Kšœ"˜"Kšœ˜Kšœ˜Kšœ˜Kšœœ˜K˜—K˜K˜—šŸ œœœ0œ˜NKšœœ˜Kšœœ ˜Kšœœ ˜#šœœœ˜3KšœU™U—Kšœ˜Kšœ˜Kšœ"˜"Kšœ˜Kšœ˜Kšœ˜K˜K˜—š Ÿœœœœœœ˜BKšœœœ ™#Kšœœœ˜Kšœ*˜*Kšœ œ ˜K˜K˜—š Ÿœœœœœ ˜0Kšœœœ˜.K˜K˜K˜—•StartOfExpansionˆ -- [recv: DriverIntercept.Recv, data: REF ANY, network: Driver.Network, buffer: Driver.Buffer, bytes: NAT] RETURNS [kill: BOOL _ FALSE]šœœœ˜Kšœ œœ˜Kšœ œœ˜Kšœœœ˜Kšœœœ˜Kšœœœ˜Kšœœœ˜K˜—–ƒ -- [recv: Driver.RecvType, data: REF ANY, network: Driver.Network, buffer: Driver.Buffer, bytes: NAT] RETURNS [kill: BOOL _ FALSE]šΟbœ!ž˜?KšΠck‹™‹Kšœœ œ˜ Kšœ"˜"Kšœ"˜"šœœ˜3šœ"˜,Kšœž˜0Kšœ˜——šœ˜K˜!K˜K˜"Kšœœ˜—šœ˜šœ ˜ šœ!œ˜9Kšœ$˜$——šœ˜šœ1˜7Kšœ$˜$——Kšœœ˜—Kšœ˜K˜—Kšœœ˜!Kšœ:Ÿ˜;šŸ œœ˜ K˜.K˜Kšœœ˜ Kšœœ˜ Kšœœ œ˜Kšœ˜K˜$K˜Kšœ"˜"˜+Kšœ˜Kšœ œœ˜Kšœ œ˜Kšœ œœ˜Kšœ˜Kšœ ˜ Kšœ œ˜K˜—š˜Kšœœœ˜Kšœ+œ˜6Kšœ9˜9K˜"Kšœ,˜,K˜ Kšœ œœ˜šœ˜Kšœ˜Kšœ œ$˜2Kšœ œ(˜8Kšœœ$˜/Kšœœ˜)Kšœœ˜&Kšœœ˜)K˜Kšœ˜—Kšœ2˜2Kšœœœ˜šœœ˜Kšœ(œ œ˜>—Kšœ˜—K˜+Kšœ˜K˜—–y -- [self: ViewerClasses.Viewer, context: Imager.Context, whatChanged: REF ANY, clear: BOOL] RETURNS [quit: BOOL _ FALSE]šœ*˜*Kš‘u™uKšœ œ ˜Kšœœ˜Kš œœœœœ˜QKšœ+˜+šœœœ˜Kšœ%™%Kšœ œ˜šœœ˜šœœ˜$Kšœ9˜9—Kšœ4˜4KšœG˜GKšœP˜PKšœM˜MKšœM˜MKšœN˜NK˜—šœœ˜Kšœ3™3Kšœ œœ˜5Kšœœ˜$Kšœ˜K˜—Kšœ˜Kšœ˜Kšœ˜—Kšœ'˜'Kšœ)˜)š˜Kšœœ˜Kšœ*œ˜/Kšœœ˜Kšœ<˜KšœF˜FKšœœ'œ˜;K˜Kšœ'˜'Kšœ œ-˜=KšœF˜FKšœœ&œ˜:K˜Kšœ'˜'Kšœ œ.˜>KšœF˜FKšœœ'œ˜;K˜Kšœ˜—Kšœ˜K˜—Kšœœ˜š œ˜ Kšœ œ˜!Kšœœ œ˜#Kšœ˜K˜—š œ˜#Kšœ œ˜šœ#˜#šœ˜Kšœ˜Kšœœ˜ Kšœ ˜ Kšœœ˜ Kšœ œ˜Kšœ ˜ —Kšœœ˜—Kšœ5˜5šœ%˜%Kšœ˜šœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ œ˜Kšœœ˜Kšœ ˜ —Kšœœ˜—Kšœ@œ˜FK˜Kšœœ˜$Kšœ6œ˜