-- Grapevine: database measuring tool

-- DBCount.mesa

-- Andrew Birrell November 10, 1982 4:11 pm

DIRECTORY
BodyDefs,
IO,
LocateDefs USING[ FindRegServer, FoundServerInfo ],
NameInfoDefs,
Process  USING[ Detach ],
ProtocolDefs,
PupDefs,
String,
UserExec USING[ CommandProc, RegisterCommand ],
ViewerIO USING[ CreateViewerStreams ];

DBCount: MONITOR
IMPORTS IO, LocateDefs, NameInfoDefs, Process, ProtocolDefs,
String, UserExec, ViewerIO =

BEGIN

OPEN BodyDefs, NameInfoDefs, ProtocolDefs, PupDefs;

serverAddr: PupDefs.PupAddress; -- other server's address --
addrKnown: BOOLEANFALSE; -- validity of serverAddr --
str: Handle ← NIL; -- stream to other server --

-- "Operate" procedure stolen from Maintain --

Operate: PUBLIC PROC[op: ProtocolDefs.RSOperation, name: BodyDefs.RName,
value: BodyDefs.RName ← NIL,
connect: BodyDefs.Connect ← NIL,
remark: BodyDefs.Remark ← NIL,
key: BodyDefs.Password ← [0,0,0,0],
sendRList: PROC[ProtocolDefs.Handle] ← NIL ]
RETURNS[ rc: ProtocolDefs.ReturnCode ] =
BEGIN
OPEN ProtocolDefs;
TryUpdate: PROC[str: ProtocolDefs.Handle] =
BEGIN
SendRSOperation[str, op];
IF op # NoOp THEN SendRName[str, name];
SELECT op FROM
IN [Expand..ReadEntry] =>
SendTimestamp[str, BodyDefs.oldestTime];
IdentifyCaller =>
SendPassword[str:str, pw: key, key: [0,0,0,0]];
IN [AddMember..DeleteFriend], NewName,
IN [IsMemberDirect..IsFriendClosure] =>
{ IF value = NIL THEN ERROR; SendRName[str, value] };
Authenticate, CreateIndividual, ChangePassword =>
SendPassword[str:str, pw: key, key: [0,0,0,0]];
ChangeConnect =>
{ IF connect = NIL THEN ERROR; SendConnect[str, connect] };
ChangeRemark =>
{ IF remark = NIL THEN ERROR; SendRemark[str, remark] };
AddListOfMembers =>
{ IF sendRList = NIL THEN ERROR; sendRList[str] };
ENDCASE => NULL;
SendNow[str];
IF op # NoOp THEN rc ← ReceiveRC[str];
END;
oldBad: BOOLEANFALSE;
Create: PROC =
BEGIN
str ← ProtocolDefs.CreateStream[serverAddr];
END;
Destroy: PROC =
BEGIN
IF str # NIL THEN DestroyStream[str];
str ← NIL;
END;
Accept: PROC[addr: PupDefs.PupAddress]RETURNS[BOOLEAN] =
BEGIN
addr.socket ← RegServerEnquirySocket;
IF str # NIL AND serverAddr # addr THEN Destroy[];
IF str = NIL
THEN BEGIN
serverAddr ← addr;
Create[ ! Failed => GOTO failed];
addrKnown ← TRUE;
END;
RETURN[TRUE];
EXITS failed => RETURN[FALSE]
END;
BEGIN
IF str # NIL
THEN BEGIN
TryUpdate[ str ! Failed => GOTO streamGone ];
EXITS streamGone => Destroy[];
END;
IF str = NIL
THEN BEGIN
IF addrKnown
THEN Create[ ! Failed => GOTO notThere]
ELSE BEGIN
[] ← LocateDefs.FindRegServer["x.GV"L, Accept];
IF str = NIL THEN GOTO notThere;
END;
TryUpdate[str ! Failed => GOTO notThere];
END;
IF rc.code = WrongServer
THEN oldBad ← TRUE
ELSE oldBad ← FALSE;
EXITS notThere => { Destroy[]; oldBad ← TRUE };
END;
IF oldBad
THEN BEGIN -- need to find the correct R-Server --
foundInfo: LocateDefs.FoundServerInfo;
foundInfo ← LocateDefs.FindRegServer[name, Accept];
WITH foundInfo SELECT FROM
notFound => rc ← [BadRName, notFound];
allDown => rc ← [AllDown,notFound];
found =>
BEGIN
TryUpdate[ str ! Failed => GOTO down ];
EXITS down =>
{ Destroy[]; rc ← [AllDown,notFound] };
END;
ENDCASE => ERROR;
END;
END;



WriteType: PROC[out: IO.STREAM, type: RNameType] =
BEGIN
out.PutRope[SELECT type FROM
group => "group",
individual => "individual",
notFound => "not found",
dead => "dead",
ENDCASE => ERROR];
END;

WriteRC: PROC[out: IO.STREAM, rc: ReturnCode] =
BEGIN
out.PutRope[SELECT rc.code FROM
done => "ok",
noChange => "no change",
outOfDate => "out of date",
NotAllowed => "not allowed",
BadOperation => "bad operation",
BadProtocol => "bad protocol",
BadRName => "bad R-Name: ",
BadPassword => "bad password",
WrongServer => "wrong server",
AllDown => "all suitable R-Servers down",
ENDCASE => ERROR];
IF rc.code = BadRName THEN WriteType[out, rc.type];
END;

Complaint: PROC[out: IO.STREAM, name: RName, rc: ReturnCode] =
BEGIN
out.PutF["\n%g", [string[name]] ];
WriteRC[out, rc];
END;

AcceptComponent: PROC[thisStr: Handle] RETURNS[length: CARDINAL] =
BEGIN
bLength: CARDINAL = 64;
buffer1: PACKED ARRAY [0..bLength) OF CHARACTER;
length1: CARDINAL ← (length ← ReceiveCount[thisStr]);
WHILE length1 > 0
DO wanted1: CARDINAL = 2*MIN[bLength/2, length1] --bytes--;
ReceiveBytes[thisStr, @buffer1, wanted1];
length1 ← length1 - wanted1/2;
ENDLOOP;
END;

-- stats increased by "Look" --
totalLength: LONG CARDINAL ← 0;
maxLength: LONG CARDINAL ← 0;
items: LONG CARDINAL ← 0;

WriteWordCount: PROC[out: IO.STREAM, n: LONG CARDINAL] =
BEGIN
out.PutF["%g words = %g pages\n", [cardinal[n]], [cardinal[(n+128)/256]] ];
END;

WriteStats: PROC[out: IO.STREAM] =
BEGIN
out.PutF[" %g items\n", [cardinal[items]] ];
IF items = 0 THEN RETURN;
out.PutRope[" total = "]; WriteWordCount[out, totalLength];
out.PutRope[" max size = "]; WriteWordCount[out, maxLength];
out.PutRope[" mean words = "]; WriteWordCount[out, totalLength/items];
totalLength ← maxLength ← items ← 0;
END;

Look: PROC[out: IO.STREAM, name: RName]RETURNS[done: BOOLEAN] =
BEGIN
thisLength: LONG CARDINAL ← 0;
stamp2: Timestamp;
rc2: ReturnCode;
rc2 ← Operate[op: ReadEntry, name: name];
IF rc2.code = done THEN stamp2 ← ReceiveTimestamp[str];
IF rc2.code # done
THEN Complaint[out, name, rc2]
ELSE BEGIN
count2: CARDINALIF rc2.code # done THEN 0
ELSE ReceiveCount[str];
FOR c: CARDINAL IN [0..count2)
DO thisLength ← thisLength + AcceptComponent[str];
ENDLOOP;
maxLength ← MAX[maxLength, thisLength];
totalLength ← totalLength + thisLength;
items ← items + 1;
END;
done ← rc2.code=WrongServer OR rc2.code=AllDown;
END;

LookEnum: PROC[out: IO.STREAM, enumName: RName,
work: PROC[IO.STREAM, RName]RETURNS[done: BOOLEAN] ] =
-- argument is "groups.reg", "individuals.reg", or "dead.reg" --
BEGIN
Enumeratee: PROC[name: RName]RETURNS[done: BOOLEAN] =
{ done ← work[out, name] };
memberInfo: MemberInfo;
out.PutChar['\n];
out.Put[[string[enumName]]];
memberInfo ← GetMembers[enumName];
WITH memberInfo SELECT FROM
group => BEGIN ENABLE UNWIND => Close[members];
out.PutChar['\n];
Enumerate[members, Enumeratee];
Close[members];
END;
allDown => out.PutRope[": all R-Servers down!"];
notFound => out.PutRope[": not found!"];
ENDCASE => ERROR;
END;

dbRegistries: LONG CARDINAL ← 0;
dbIndividuals: LONG CARDINAL ← 0;
dbGroups: LONG CARDINAL ← 0;
dbDead: LONG CARDINAL ← 0;
dbSize: LONG CARDINAL ← 0;

LookAtRegistry: PROC[out: IO.STREAM, regGroup: RName] RETURNS[done: BOOLEAN] =
BEGIN
regTotal: LONG CARDINAL ← 0;
wanted: RName = [maxRNameLength];
FOR i: CARDINAL DECREASING IN [0..regGroup.length)
DO regGroup.length ← regGroup.length-1;
IF regGroup[i] = '. THEN EXIT;
ENDLOOP;
out.PutF["\n\nRegistry %g\n", [string[regGroup]]];
String.AppendString[wanted, "Groups."L];
String.AppendString[wanted, regGroup];
LookEnum[out, wanted, Look];
dbGroups ← dbGroups + items; regTotal ← regTotal + totalLength;
WriteStats[out];
wanted.length ← 0;
String.AppendString[wanted, "Individuals."L];
String.AppendString[wanted, regGroup];
LookEnum[out, wanted, Look];
dbIndividuals ← dbIndividuals + items; regTotal ← regTotal + totalLength;
WriteStats[out];
wanted.length ← 0;
String.AppendString[wanted, "Dead."L];
String.AppendString[wanted, regGroup];
LookEnum[out, wanted, Look];
dbDead ← dbDead + items; regTotal ← regTotal + totalLength;
WriteStats[out];
out.PutRope["\nTotal registry size = "]; WriteWordCount[out, regTotal];
dbRegistries ← dbRegistries + 1;
dbSize ← dbSize + regTotal;
done ← FALSE;
END;

LookAtAll: ENTRY PROC =
BEGIN
out: IO.STREAM = ViewerIO.CreateViewerStreams["GV database size measurement"].out;
LookEnum[out, "Groups.gv", LookAtRegistry ! ProtocolDefs.Failed =>
{ out.PutRope["ProtocolDefs.Failed"]; CONTINUE }];
out.PutF["\n\nEntire database contains:\n %g registries\n %g groups\n %g individuals\n %g dead entries\n Total size = ",
[cardinal[dbRegistries]],
[cardinal[dbGroups]],
[cardinal[dbIndividuals]],
[cardinal[dbDead]] ];
WriteWordCount[out, dbSize];
IF str # NIL THEN DestroyStream[str];
END;


DoIt: UserExec.CommandProc = TRUSTED
BEGIN
ProtocolDefs.Init[];
Process.Detach[FORK LookAtAll[]];
END;

UserExec.RegisterCommand["DBCount", DoIt, "Gather size statistics on GV database"];



END.