<> <> <> <> 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] = { < log IN [0..height]>> 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 <<[recv: CommDriver.RecvType, data: REF ANY, network: CommDriver.Network, buffer: CommDriver.Buffer, bytes: NAT] RETURNS [kill: BOOL _ FALSE]>> 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 = { <<[self: ViewerClasses.Viewer, context: Imager.Context, whatChanged: REF ANY, clear: BOOL] RETURNS [quit: BOOL _ FALSE]>> 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"]; }.