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
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: BOOLFALSE,
watcher: PROCESSNIL
];
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]];
Imager.MaskRectangle[context, [x: -left, y: 0, w: left+data.width+left, h: 1]];
};
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]];
Imager.MaskRectangle[context, [x: -left, y: 0, w: left+data.width+left, h: 1]];
};
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]];
Mask off the number region
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 {
There is something worth plotting. Make sure that it gets at least one pixel.
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;
This forces us to paint fully, since we have wrapped around and are now losing plots.
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] = {
n IN [0..1.0] => 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 {
We must paint in the Frames and stuff
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 {
The window width has changed, so refigure the width
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"];
}.