-- Transport Mechanism Filestore - heap measuring tool --
-- [Juniper]<Grapevine>MS>HeapCount.mesa
-- Andrew Birrell 18-Feb-82 14:02:41 --
DIRECTORY
Ascii USING[ CR, DEL, SP ],
BodyDefs USING[ maxRNameLength, RName ],
GlassDefs USING[ Handle, Listen, TimeOut ],
HeapDefs USING[ objectStart ],
HeapFileDefs USING[ FirstSegment, NextPage, NoMorePages ],
HeapXDefs USING[ PageHeader, ObjectHeader, ReaderData],
LogDefs USING[ WriteLogEntry ],
NameInfoDefs USING[ Authenticate, IsMemberClosure, Membership ],
ObjectDir USING[ DirData, FindData, LOCK, ReleaseData, zeroCount ],
ObjectDirDefs USING[ ObjectType ],
ObjectDirXDefs USING[ ObjectNumber, ObjectState, gapObjectNumber ],
Policy USING[ compactorEnabled, compactorPause, compactorStart,
LOCK, secsCond ],
Process USING[ Detach, DisableTimeout, InitializeCondition,
InitializeMonitor ],
RestartDefs USING[],
String USING[ AppendChar, AppendString ],
VMDefs USING[ Deactivate, Page, PageIndex, ReadPage,
PageAddress, PageNumber, FullAddress ];
Test: MONITOR LOCKS l USING l: POINTER TO MONITORLOCK
IMPORTS GlassDefs, HeapFileDefs, LogDefs, NameInfoDefs, ObjectDir,
Policy, Process, String, VMDefs
EXPORTS RestartDefs
SHARES ObjectDir, Policy =
BEGIN
OPEN HeapXDefs, ObjectDirXDefs;
-- User interface --
LogAction: PROC[user, action: STRING] =
BEGIN
log: STRING = [128];
String.AppendString[log, action];
String.AppendString[log, " caused by "L];
String.AppendString[log, user];
LogDefs.WriteLogEntry[log];
END;
LowerCase: PROC[c: CHARACTER] RETURNS[CHARACTER] = INLINE
{ RETURN[ IF c IN ['A..'Z] THEN 'a + (c-'A) ELSE c ] };
BeginsWith: PROC[a, b: STRING] RETURNS[ BOOLEAN ] =
BEGIN
FOR i: CARDINAL IN [0..b.length)
DO IF i >= a.length OR LowerCase[a[i]] # LowerCase[b[i]]
THEN RETURN[FALSE];
ENDLOOP;
RETURN[TRUE]
END;
Command: TYPE = { enable, login, quit, map, stats };
Del: ERROR = CODE;
LoginState: TYPE = { none, ok, enabled };
Login: PROC[str: GlassDefs.Handle, user, pwd: STRING]
RETURNS[ok: BOOLEAN] =
BEGIN
OPEN str;
default: STRING = ".pa"L;
WriteChar[Ascii.CR];
IF ReadString["Your name please: "L, user, word] = Ascii.DEL
THEN ERROR Del[];
WriteChar[Ascii.CR];
SELECT ReadString["Your password: "L, pwd, pwd] FROM
Ascii.DEL => ERROR Del[];
Ascii.SP =>
BEGIN
acc: STRING = [8];
[] ← ReadString[" (account): "L, acc, word];
END;
ENDCASE => NULL;
WriteString[" ... "L]; SendNow[]; ok ← FALSE;
SELECT NameInfoDefs.Authenticate[user, pwd] FROM
individual => { WriteString["ok"L]; ok ← TRUE };
group => WriteString["Can't login as a group"L];
notFound => WriteString["Not a valid user name"L];
badPwd => WriteString["Incorrect password"L];
allDown => WriteString["Can't contact authentication server"L];
ENDCASE => ERROR;
END;
Enable: PROC[str: GlassDefs.Handle, user: STRING]
RETURNS[ privileged: BOOLEAN ] =
BEGIN
OPEN str;
privileged ← FALSE;
SELECT NameInfoDefs.IsMemberClosure["Transport↑.ms"L, user] FROM
yes => { WriteString["ok"L]; privileged ← TRUE };
allDown => WriteString["can't contact access control server"L];
no, notGroup => WriteString["not privileged"L];
ENDCASE;
END;
ChooseCommand: PROC[str: GlassDefs.Handle, state: LoginState]
RETURNS[Command] =
BEGIN
OPEN str;
names: ARRAY Command OF STRING = [
enable: "Enable"L,
login: "Login"L,
quit: "Quit"L,
map: "Map"L,
stats: "Statistics"L ];
allowed: PACKED ARRAY Command OF { yes, no } = SELECT state FROM
none => [
enable: no,
login: yes,
quit: yes,
map: no,
stats: no ],
ok => [
enable: yes,
login: yes,
quit: yes,
map: no,
stats: no ],
enabled => [
enable: no,
login: yes,
quit: yes,
map: yes,
stats: yes ],
ENDCASE => ERROR;
buff: STRING = [64];
prompt: STRING = IF state = enabled THEN "HC! "L ELSE "HC: "L;
WriteString[prompt];
DO c: CHARACTER = LowerCase[ReadChar[]];
FOR i: Command IN Command
DO IF allowed[i] = yes
AND BeginsWith[names[i], buff]
AND c = LowerCase[names[i][buff.length]]
THEN BEGIN
FOR j: CARDINAL IN [buff.length..names[i].length)
DO nChar: CHARACTER = names[i][j];
String.AppendChar[buff, nChar]; WriteChar[nChar];
IF nChar = Ascii.SP THEN EXIT;
REPEAT FINISHED => RETURN[i]
ENDLOOP;
EXIT
END
REPEAT FINISHED =>
BEGIN
SELECT c FROM
'? =>
BEGIN
first: BOOLEAN ← TRUE;
WriteString["? One of: "L];
FOR i: Command IN Command
DO IF allowed[i] = yes
AND BeginsWith[names[i], buff]
THEN BEGIN
IF first THEN first←FALSE ELSE WriteString[", "L];
FOR j: CARDINAL IN [buff.length..names[i].length)
DO WriteChar[names[i][j]] ENDLOOP;
END;
ENDLOOP;
END;
Ascii.DEL => ERROR Del[];
ENDCASE => { WriteChar[c]; WriteChar['?] };
WriteChar[Ascii.CR]; WriteString[prompt]; WriteString[buff];
END;
ENDLOOP;
ENDLOOP;
END;
Receive: PROC[str: GlassDefs.Handle] =
BEGIN
OPEN str;
state: LoginState ← none;
user: BodyDefs.RName = [BodyDefs.maxRNameLength];
pwd: STRING = [16];
WriteChar[Ascii.CR];
WriteString["Grapevine Server Heap-counter"L];
WriteChar[Ascii.CR];
DO WriteChar[Ascii.CR];
BEGIN
ENABLE Del => GOTO del;
comm: Command ← ChooseCommand[str, state !
GlassDefs.TimeOut => GOTO timeOut];
WriteString[" ... "L]; SendNow[];
SELECT comm FROM
enable => IF Enable[str, user] THEN state ← enabled;
login => { state ← none; -- because Login changes "user"
IF Login[str, user, pwd] THEN state ← ok };
quit => EXIT;
stats => { LogAction[user, "Heap-counter"L]; Count[str,FALSE] };
map => { LogAction[user, "Heap-mapper"L]; Count[str,TRUE] };
ENDCASE => WriteString["Unimplemented command"L];
EXITS
timeOut =>
BEGIN
WriteString["Type any character to continue ... "L];
[] ← ReadChar[ ! GlassDefs.TimeOut => GOTO end ];
EXITS end =>
{ WriteString["good-bye"L]; EXIT }
END;
del =>
{ Flush[]; WriteString[" XXX"L] };
END;
ENDLOOP;
END;
Work: PROC =
{ DO GlassDefs.Listen[Receive, [0,77B]] ENDLOOP };
-- Statistics --
unused: ARRAY ObjectDirDefs.ObjectType OF CARDINAL;
-- number of zero reference-count objects of each type --
unusedWords: ARRAY ObjectDirDefs.ObjectType OF LONG CARDINAL;
-- total lengths of unused objects of each type --
used: ARRAY ObjectDirDefs.ObjectType OF CARDINAL;
-- number of non-zero reference-count objects of each type --
references: ARRAY ObjectDirDefs.ObjectType OF LONG CARDINAL;
-- number of references to objects of each type --
usedWords: ARRAY ObjectDirDefs.ObjectType OF LONG CARDINAL;
-- total lengths of used objects of each type --
avWords: ARRAY ObjectDirDefs.ObjectType OF LONG CARDINAL;
-- avLength[i] = usedLength[i] / used[i] --
avRefs: ARRAY ObjectDirDefs.ObjectType OF LONG CARDINAL;
-- avRefs[i] = references[i] / used[i] --
gapWords: LONG CARDINAL;
-- amount of space not occupied by objects --
pages: CARDINAL;
-- number of pages in written part of heap --
words: LONG CARDINAL;
-- pages * (VMDefs.pageSize-SIZE[PageHeader]) --
InitStats: PROC =
BEGIN
unused ← ALL[0];
unusedWords ← ALL[0];
used ← ALL[0];
references ← ALL[0];
usedWords ← ALL[0];
avRefs ← ALL[0];
avWords ← ALL[0];
gapWords ← 0;
pages ← 0;
words ← 0;
END;
WriteStats: PROC[str: GlassDefs.Handle] =
BEGIN
OPEN str;
Add: PROC[i, j: ObjectDirDefs.ObjectType] =
BEGIN
unused[i] ← unused[i] + unused[j];
unusedWords[i] ← unusedWords[i] + unusedWords[j];
used[i] ← used[i] + used[j];
references[i] ← references[i] + references[j];
usedWords[i] ← usedWords[i] + usedWords[j];
END;
Write: PROC[i: ObjectDirDefs.ObjectType, s: STRING] =
BEGIN
WriteString[s]; WriteChar[' ];
WriteDecimal[unused[i]]; WriteChar[' ];
WriteDecimal[used[i]]; WriteChar[' ];
WriteLongDecimal[unusedWords[i]]; WriteChar[' ];
WriteLongDecimal[usedWords[i]]; WriteChar[' ];
WriteLongDecimal[avWords[i]]; WriteChar[' ];
WriteLongDecimal[references[i]]; WriteChar[' ];
WriteLongDecimal[avRefs[i]]; WriteChar[Ascii.CR];
END;
WriteString["
Type object-counts: word-counts: av. references:
unused used unused used words total average
"L];
FOR i: ObjectDirDefs.ObjectType IN ObjectDirDefs.ObjectType
DO IF used[i] # 0
THEN BEGIN
avWords[i] ← usedWords[i] / used[i];
avRefs[i] ← references[i] / used[i];
END;
IF i # spare17 THEN Add[spare17, i];
Write[i, SELECT i FROM
gap => "gap"L,
body => "body"L,
SLinput => "SLinput"L,
SLpending => "SLpend"L,
SLforward => "SLfwd"L,
RSobject => "RSobj"L,
RSmail => "RSmail"L,
temp => "temp"L,
RSname => "RSname"L,
MSname => "MSname"L,
testMode => "test"L,
TOC => "TOC"L,
archived => "archive"L,
delArch => "spare15"L,
spare16 => "spare16"L,
spare17 => "total"L,
ENDCASE => ERROR ];
ENDLOOP;
WriteString["There are "L]; WriteLongDecimal[gapWords];
WriteString[" gap words.
"L];
words ← LONG[pages] * ( 256 - SIZE[HeapXDefs.PageHeader] );
WriteString["The written area contains "L]; WriteDecimal[pages];
WriteString[" pages = "L]; WriteLongDecimal[words]; WriteString[" words."L];
END;
-- HeapMap info --
BriefType: PROC[str: GlassDefs.Handle, i: ObjectDirDefs.ObjectType] =
BEGIN
str.WriteString[SELECT i FROM
gap => "."L,
body => "B"L,
SLinput => "S"L,
SLpending => "S"L,
SLforward => "S"L,
RSobject => "R"L,
RSmail => "RSmail"L,
temp => "temp"L,
RSname => "RSname"L,
MSname => "MSname"L,
testMode => "test"L,
TOC => "T"L,
archived => "A"L,
delArch => "spare15"L,
spare16 => "spare16"L,
spare17 => "spare17"L,
ENDCASE => ERROR ];
END;
MapInfo: PROC[str: GlassDefs.Handle] =
BEGIN
OPEN str;
WriteString["
""B"" = body, ""S"" = steering-list, ""R"" = RSobject, ""T"" = TOC, ""A"" = archive; ""x?"" = unused object of type ""x""; ""-"" for each non-gap sub-object, ""."" for each gap sub-object; <sp> between pages, <cr> at non-contiguous page.
"L];
END;
-- Procedure almost stolen from ObjectDir, but with side-effect removed --
GetObjectState: INTERNAL PROC[ obj: ObjectDirXDefs.ObjectNumber,
where: VMDefs.FullAddress ]
RETURNS[ state: ObjectDirXDefs.ObjectState ] =
BEGIN
OPEN ObjectDir;
either: POINTER TO DirData = FindData[obj];
WITH data: either SELECT FROM
used => IF data.page # where.page.page
THEN { state ← duplicate; ReleaseData[clean] }
ELSE IF data.count = zeroCount
THEN BEGIN
unused[obj.type] ← unused[obj.type] + 1;
state ← unused; ReleaseData[clean];
END
ELSE BEGIN
used[obj.type] ← used[obj.type] + 1;
references[obj.type] ← references[obj.type] +
data.count - zeroCount;
state ← inUse; ReleaseData[clean];
END;
ENDCASE => ERROR --object number not in use --;
END;
-- Hack to stop compactor --
StopCompactor: ENTRY PROC[ l: POINTER TO MONITORLOCK ← @Policy.LOCK ] =
BEGIN
Policy.compactorEnabled ← FALSE;
THROUGH [1..5] DO WAIT Policy.secsCond ENDLOOP;
END;
StartCompactor: ENTRY PROC[ l: POINTER TO MONITORLOCK ← @Policy.LOCK ] =
BEGIN
Policy.compactorEnabled ← TRUE;
NOTIFY Policy.compactorStart;
NOTIFY Policy.compactorPause;
END;
-- Current state --
reader: HeapXDefs.ReaderData;
Start: PROCEDURE =
BEGIN
reader.where ← HeapFileDefs.FirstSegment[];
reader.page ← VMDefs.ReadPage[reader.where.page, 2];
reader.offset ← HeapDefs.objectStart;
reader.object ← ObjectDirXDefs.gapObjectNumber;
InitStats[];
END;
ScanHeap: ENTRY PROCEDURE[ str: GlassDefs.Handle, map: BOOLEAN,
l: POINTER TO MONITORLOCK ← @ObjectDir.LOCK ] =
BEGIN
OPEN VMDefs, str;
prevPage: VMDefs.PageNumber ← PRED[LAST[VMDefs.PageNumber]];
state: { gap, unused, inUse } ← gap;
reader.object ← gapObjectNumber --indicates "no current object"--;
reader.end ← FALSE;
DO -- Consider any page header --
IF reader.where.word = FIRST[PageIndex]
THEN BEGIN
pageHead: POINTER TO PageHeader = LOOPHOLE[reader.page,POINTER]
+ reader.where.word;
IF map
THEN BEGIN
IF reader.where.page.page # 1+prevPage
THEN { WriteChar[Ascii.CR];
WriteDecimal[reader.where.page.page] };
WriteChar[Ascii.SP];
prevPage ← reader.where.page.page;
END;
reader.where.word ← reader.where.word + SIZE[ PageHeader ];
reader.offset ← pageHead.offset;
END
ELSE reader.offset ← HeapDefs.objectStart;
BEGIN -- read sub-object header --
object: POINTER TO ObjectHeader =
LOOPHOLE[reader.page,POINTER] + reader.where.word;
IF ( reader.object # gapObjectNumber
-- Inside an object, looking for continuation sub-object --
-- If a duplicate start is found, it may be non-ignorable --
AND object.number = reader.object
AND reader.offset = HeapDefs.objectStart )
OR ( object.number # gapObjectNumber
AND reader.offset = HeapDefs.objectStart )
THEN BEGIN -- start of a new object --
SELECT GetObjectState[object.number, reader.where] FROM
inUse => BEGIN
reader.object ← object.number;
IF map
THEN { BriefType[str, object.number.type] };
state ← inUse
END;
unused => -- ignorable object --
BEGIN
IF map
THEN { BriefType[str, object.number.type];
WriteChar['?] };
state ← unused;
END;
duplicate => NULL; -- ignorable object --
ENDCASE => ERROR;
reader.object ← object.number --now the current object--;
END
-- ELSE we have one of:
-- continuation of ignorable object,
-- imbedded object which should be ignored,
-- unexpected partial object,
-- gap object --;
reader.where.word ← reader.where.word + SIZE[ObjectHeader];
reader.where.word ← reader.where.word + object.size;
SELECT TRUE FROM
state = inUse AND object.number = reader.object =>
{ usedWords[object.number.type] ← usedWords[object.number.type]
+ SIZE[ObjectHeader] + object.size;
IF map THEN WriteChar['-];
};
state = unused AND object.number = reader.object =>
{ unusedWords[object.number.type] ←
unusedWords[object.number.type]
+ SIZE[ObjectHeader] + object.size;
IF map THEN WriteChar['-];
};
ENDCASE => { gapWords ← gapWords + SIZE[ObjectHeader] + object.size;
IF map THEN WriteChar['.] };
END;
-- check for end of page --
IF reader.where.word + SIZE[ObjectHeader] > LAST[PageIndex]
THEN BEGIN
pages ← pages + 1;
Deactivate[reader.page]; -- not "Release", for better cache -
reader.where ← HeapFileDefs.NextPage[reader.where !
HeapFileDefs.NoMorePages => EXIT];
-- That may have generated a signal --
-- Note that there is no current page here. --
reader.page ← ReadPage[reader.where.page, 2];
END
ELSE -- end of any current object --
BEGIN
reader.object ← gapObjectNumber;
state ← gap;
END;
ENDLOOP;
END;
Count: PROCEDURE[str: GlassDefs.Handle, map: BOOLEAN] =
BEGIN
str.WriteString["I'm working on it ... "L];
IF map THEN MapInfo[str];
str.SendNow[];
StopCompactor[];
Start[];
ScanHeap[str, map];
StartCompactor[];
WriteStats[str];
END --Count--;
-- Main Program --
Process.InitializeMonitor[ @reader.LOCK ];
Process.InitializeCondition[ @reader.canStart, 0 ];
Process.DisableTimeout[ @reader.canStart ];
reader.stopped ← FALSE;
Process.Detach[FORK Work[]]
END.