-- 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.