-- RStatsImpl.Mesa, last edit January 18, 1983 1:20 pm -- Pilot 6.0/ Mesa 7.0 -- -- to compute statistics -- -- RStats -i Cedar3.1.DF -- -- then -- RStats -b for the bcds -- (counts bcds, sizes, etc.) -- RStats -d Cedar3.1.DF for the DF files -- (counts DF files, produces DF-DF xref and file-DF xref) -- RStats -e for the bcd dependency analysis -- (for bcd definitions file - DF file xref) -- RStats -f for the bcd dependency analysis -- (where Special files (from a list) are looked for, e.g. Exec) -- RStats -r for the source files (broken down by every subdirectory) -- (source file totals) -- RStats -s for the source files -- (source file totals) -- RStats -t for totals on # and size of files -- (overall totals) -- alternatively -- RStats -a Cedar3.1.DF -- which does a "-i, -d, -b, -s, -t" in sequence (takes about 3 hours) DIRECTORY BcdDefs: TYPE USING[Base, MTIndex], BcdOps: TYPE USING[BcdBase, MTHandle], ConvertUnsafe: TYPE USING[ToRope], CS: TYPE USING[EquivalentRS, Flat, Init, z], CWF: TYPE USING[SWF4, SetWriteProcedure], DFSubr: TYPE USING[StripLongName], Directory: TYPE USING[DeleteFile, Error], FileIO: TYPE USING[Open], FQ: TYPE USING[FileQuery, Result], IFSFile: TYPE USING[CantOpen, Close, Error, FileHandle, GetLength, UnableToLogin], IO: TYPE USING[card, CharProc, Close, CreateDribbleStream, EndOf, Flush, GetSequence, Handle, int, Put, PutChar, PutF, PutFR, ResetUserAbort, rope, string, UserAborted], LeafSubr: TYPE USING[PrintLeafAccessFailure, PrintLeafProblem, RemoteMap, StopLeaf], Process: TYPE USING[Detach], Rope: TYPE USING[Cat, Fetch, Flatten, ROPE, Text], RStatsSupport: TYPE USING[BcdDep, BreakUp, ComputeFileList, DFDep, DFRec, GetLine, LeafOpenWithCreate, ProcessBcdAnalysis, ProcessDFList, SubString], Space: TYPE USING[Create, Handle, LongPointer, nullHandle, Unmap, virtualMemory], UnsafeSTP: TYPE USING[Error, FileInfo, GetFileInfo, Handle], STPSubr: TYPE USING [EnumerateForRetrieve, RetrieveProcType, StopSTP], Subr: TYPE USING [AbortMyself, EndsIn, errorflg, FileError, MakeTTYProcs, Prefix, PrintGreeting, strcpy, SubrStop, TTYProcs], Time: TYPE USING[Current], TypeScript: TYPE USING[TS, Create, UserAbort, ResetUserAbort], UECP: TYPE USING[Argv, Parse], UserExec: TYPE USING[AskUser, CommandProc, RegisterCommand], ViewerClasses: TYPE USING[Viewer], ViewerEvents: TYPE USING[EventProc, EventRegistration, RegisterEventProc, UnRegisterEventProc], ViewerIO: TYPE USING[CreateViewerStreams]; RStatsImpl: PROGRAM IMPORTS ConvertUnsafe, CS, CWF, DFSubr, Directory, FileIO, FQ, IFSFile, IO, LeafSubr, Process, Rope, RStatsSupport, Space, STP: UnsafeSTP, STPSubr, Subr, Time, TypeScript, UECP, UserExec, ViewerEvents, ViewerIO = { Stat: TYPE = {nBcd, nBcdDefn, nBcdConfig, nBcdImpl, nBcdCodeBytes, nBcdFileBytes, nSrc, nSrcConfig, nSrcMesa, nSrcImpl, nSrcDefn, nSrcLines, nSrcBytes, nDF, nOtherFiles, nOtherBytes, nTotalFiles, nTotalBytes}; StatArray: TYPE = ARRAY Stat OF INT; DFRec: TYPE = RStatsSupport.DFRec; DFDep: TYPE = RStatsSupport.DFDep; BcdDep: TYPE = RStatsSupport.BcdDep; -- check ResetCtrs[] before adding more global state Global: TYPE = RECORD[ fileListName: Rope.Text ← NIL, -- this is readonly xrefFileName: Rope.Text ← NIL, -- this is readonly space: Space.Handle ← Space.nullHandle, stat: REF StatArray ← NIL, totalStat: REF StatArray ← NIL, dfrec: LIST OF REF DFRec ← NIL, -- stores list of DF files lastHost: Rope.Text ← NIL, lastDirectory: Rope.Text ← NIL, lastShortname: Rope.Text ← NIL, lastTopDirectory: Rope.Text ← NIL, lastWholeDirectory: Rope.Text ← NIL, -- -- viewer data typeScript: TypeScript.TS ← NIL, in: IO.Handle ← NIL, out: IO.Handle ← NIL, -- dribblestream fout: IO.Handle ← NIL, -- backing file tty: Subr.TTYProcs ← NIL ]; -- MDS usage g: REF Global ← NEW[Global ← [space: Space.Create[1, Space.virtualMemory], stat: NEW[StatArray], totalStat: NEW[StatArray], fileListName: "ReleaseStats.Files", xrefFileName: "XRef.DFXRef"]]; destroyEventRegistration: ViewerEvents.EventRegistration; -- end of MDS usage Choice: TYPE = {none, all, bcd, df, bcdAnalysis, bcdAnalysisSpecialFiles, init, src, detailSrc, total}; Main: UserExec.CommandProc = TRUSTED { argv: UECP.Argv; token, filename: Rope.Text; choice: Choice ← none; p: PROCESS; CS.Init[]; -- to make sure module is started argv ← UECP.Parse[event.commandLine]; FOR i: CARDINAL IN [1 .. argv.argc) DO token ← CS.Flat[argv[i]]; IF token.Fetch[0] = '- THEN SELECT token.Fetch[1] FROM 'a => choice ← all; 'b => choice ← bcd; 'd => choice ← df; 'e => choice ← bcdAnalysis; 'f => choice ← bcdAnalysisSpecialFiles; 'i => choice ← init; 'r => choice ← detailSrc; 's => choice ← src; 't => choice ← total; ENDCASE => NULL ELSE IF choice = init OR choice = df OR choice = all THEN filename ← token; ENDLOOP; IF choice = all THEN p ← FORK RunAll[filename] ELSE p ← FORK ChooseOne[choice, filename]; Process.Detach[p]; }; -- this procedure is FORKed RunAll: PROC[filename: Rope.Text] = { ChooseOne[init, filename]; ChooseOne[df, filename]; ChooseOne[src, filename]; ChooseOne[bcd, filename]; ChooseOne[total, filename]; }; -- this procedure is FORKed (or called by RunAll) ChooseOne: PROC[choice: Choice, filename: Rope.Text] = { timeStarted: LONG CARDINAL ← Time.Current[]; Cleanup: PROC = { TypeScript.ResetUserAbort[g.typeScript]; PrintStat[g.out, g.stat, choice]; PrintStat[g.out, g.totalStat, choice]; ResetCtrs[]; LeafSubr.StopLeaf[]; STPSubr.StopSTP[]; Subr.SubrStop[]; timeStarted ← Time.Current[] - timeStarted; g.out.PutF["Elapsed time for RStats: %r\n", IO.card[timeStarted]]; g.out.Put[IO.string["-----------------------\n\n"L]]; g.fout.Flush[]; -- since g.out.Flush may not flush it (bugs) g.out.Flush[]; -- cannot g.out.Close, since it destroys the viewer }; { ENABLE { UNWIND => Cleanup[]; STP.Error => { lcode: LONG CARDINAL ← LOOPHOLE[code, CARDINAL]; g.out.Put[IO.string["FTP Error. "L]]; IF error ~= NIL THEN g.out.PutF["message: %s, code %d in Stp.Mesa\n", IO.string[error], IO.card[lcode]]; Subr.errorflg ← TRUE; GOTO leave; }; IFSFile.Error, IFSFile.UnableToLogin => { LeafSubr.PrintLeafProblem[reason]; GOTO leave; }; IFSFile.CantOpen => { LeafSubr.PrintLeafAccessFailure[reason]; GOTO leave; }; Subr.AbortMyself, ABORTED, IO.UserAborted => { g.out.ResetUserAbort[]; g.out.Put[IO.string["RStats Aborted.\n"L]]; GOTO leave; }; }; Subr.PrintGreeting["RStats"L]; ResetCtrs[]; IF choice = init THEN RStatsSupport.ComputeFileList[LOOPHOLE[filename], g.fileListName, g.typeScript, g.out, g.tty] ELSE AnalyzeAll[choice, filename, g.typeScript, g.out, g.tty]; EXITS leave => NULL; }; Cleanup[]; }; AnalyzeAll: PROC[choice: Choice, fileName: Rope.Text, typeScript: TypeScript.TS, out: IO.Handle, tty: Subr.TTYProcs] = { file: IO.Handle ← FileIO.Open[g.fileListName, read]; sfn: Rope.Text; version: CARDINAL; createtime: LONG CARDINAL; host: STRING ← [100]; directory: STRING ← [100]; shortname: STRING ← [100]; out.PutF["Reading list of files from %s\n", IO.rope[g.fileListName]]; WHILE NOT file.EndOf[] DO [sfn, createtime] ← RStatsSupport.BreakUp[file.GetSequence[LinePlusCR]]; version ← DFSubr.StripLongName[LOOPHOLE[sfn], host, directory, shortname]; IF choice = src AND (Subr.EndsIn[shortname, ".Mesa"L] OR Subr.EndsIn[shortname, ".Config"L]) THEN { IF SkipFile[host, directory, shortname] THEN LOOP; CheckForTopDirSwitch[directory, out, choice]; AnalyzeSrc[host, directory, shortname, version, createtime, out, tty] } ELSE IF choice = detailSrc AND (Subr.EndsIn[shortname, ".Mesa"L] OR Subr.EndsIn[shortname, ".Config"L]) THEN { IF SkipFile[host, directory, shortname] THEN LOOP; CheckForWholeDirSwitch[directory, out, choice]; AnalyzeSrc[host, directory, shortname, version, createtime, out, tty] } ELSE IF choice = bcd AND Subr.EndsIn[shortname, ".Bcd"L] THEN { IF SkipFile[host, directory, shortname] THEN LOOP; CheckForTopDirSwitch[directory, out, choice]; AnalyzeBcd[host, directory, shortname, version, createtime, out, tty] } ELSE IF (choice = df OR choice = bcdAnalysis OR choice = bcdAnalysisSpecialFiles) AND Subr.EndsIn[shortname, ".DF"L] THEN g.dfrec ← CONS[CS.z.NEW[DFRec ← [host: ConvertUnsafe.ToRope[host], directory: ConvertUnsafe.ToRope[directory], shortname: ConvertUnsafe.ToRope[shortname], version: version, dep: NIL, refBy: NIL]], g.dfrec] ELSE IF choice = total THEN { IF SkipFile[host, directory, shortname] THEN LOOP; CheckForTopDirSwitch[directory, out, choice]; AnalyzeTotals[LOOPHOLE[sfn], host, directory, shortname, version, createtime, out, tty] }; IF TypeScript.UserAbort[typeScript] THEN SIGNAL Subr.AbortMyself; out.Flush[]; ENDLOOP; file.Close[]; out.Flush[]; IF choice = df THEN g.stat[nDF] ← RStatsSupport.ProcessDFList[fileName, typeScript, out, tty, g.dfrec, g.xrefFileName]; IF choice = bcdAnalysis OR choice = bcdAnalysisSpecialFiles THEN RStatsSupport.ProcessBcdAnalysis[typeScript, out, tty, g.dfrec, choice = bcdAnalysisSpecialFiles]; }; LinePlusCR: IO.CharProc = TRUSTED { RETURN[quit: char = '\n, include: TRUE]; }; SkipFile: PROC[host, directory, shortname: LONG STRING] RETURNS[skipIt: BOOL] = { IF g.lastHost ~= NIL THEN skipIt ← CS.EquivalentRS[g.lastShortname, shortname] AND CS.EquivalentRS[g.lastDirectory, directory] AND CS.EquivalentRS[g.lastHost, host] ELSE skipIt ← FALSE; IF NOT skipIt THEN { g.lastShortname ← ConvertUnsafe.ToRope[shortname]; IF NOT CS.EquivalentRS[g.lastDirectory, directory] THEN g.lastDirectory ← ConvertUnsafe.ToRope[directory]; IF NOT CS.EquivalentRS[g.lastHost, host] THEN g.lastHost ← ConvertUnsafe.ToRope[host]; }; }; CheckForWholeDirSwitch: PROC[directory: LONG STRING, out: IO.Handle, choice: Choice] = { IF NOT CS.EquivalentRS[g.lastWholeDirectory, directory] THEN { IF g.lastWholeDirectory ~= NIL THEN PrintStat[out, g.stat, choice]; g.lastWholeDirectory ← ConvertUnsafe.ToRope[directory]; }; }; CheckForTopDirSwitch: PROC[directory: LONG STRING, out: IO.Handle, choice: Choice] = { len: CARDINAL ← directory.length; dir: STRING ← [100]; FOR i: CARDINAL IN [0 .. len) DO IF directory[i] = '> THEN { len ← i; EXIT; }; ENDLOOP; Subr.strcpy[dir, directory]; dir.length ← len; IF NOT CS.EquivalentRS[g.lastTopDirectory, dir] THEN { IF g.lastTopDirectory ~= NIL THEN PrintStat[out, g.stat, choice]; g.lastTopDirectory ← ConvertUnsafe.ToRope[dir]; }; }; -- total analysis AnalyzeTotals: PROC[filename, host, directory, shortname: LONG STRING, version: CARDINAL, createtime: LONG CARDINAL, out: IO.Handle, tty: Subr.TTYProcs] = { fres: FQ.Result; targetFileName: STRING ← [125]; vnum: CARDINAL; byteLength: LONG CARDINAL; -- out.PutF["<%s>%s", IO.string[directory], IO.string[shortname]]; [fres: fres, remoteVersion: vnum, remoteByteLength: byteLength] ← FQ.FileQuery[host, directory, shortname, version, createtime, FALSE, tty, targetFileName]; SELECT fres FROM foundCorrectVersion => { IF Subr.EndsIn[shortname, ".Mesa"L] OR Subr.EndsIn[shortname, ".Config"L] THEN { g.stat[nSrc] ← g.stat[nSrc] + 1; g.stat[nSrcBytes] ← g.stat[nSrcBytes] + byteLength } ELSE IF Subr.EndsIn[shortname, ".Bcd"L] THEN { g.stat[nBcd] ← g.stat[nBcd] + 1; g.stat[nBcdFileBytes] ← g.stat[nBcdFileBytes] + byteLength } ELSE { g.stat[nOtherFiles] ← g.stat[nOtherFiles] + 1; g.stat[nOtherBytes] ← g.stat[nOtherBytes] + byteLength; }; g.stat[nTotalFiles] ← g.stat[nTotalFiles] + 1; g.stat[nTotalBytes] ← g.stat[nTotalBytes] + byteLength; }; foundWrongVersion, notFound => out.PutF[" Error - cannot open %s.", IO.string[targetFileName]]; ENDCASE => ERROR; -- out.PutChar['\n]; }; -- source procedures AnalyzeSrc: PROC[host, directory, shortname: LONG STRING, version: CARDINAL, createtime: LONG CARDINAL, out: IO.Handle, tty: Subr.TTYProcs] = { line: STRING ← [2000]; -- this is only called once OneFile: STPSubr.RetrieveProcType = { info: STP.FileInfo ← STP.GetFileInfo[stp]; nImpl, nDefs: INT ← 0; nLines: INT ← 0; checkForType: BOOL ← TRUE; IF Subr.EndsIn[shortname, ".Config"L] THEN g.stat[nSrcConfig] ← g.stat[nSrcConfig] + 1 ELSE g.stat[nSrcMesa] ← g.stat[nSrcMesa] + 1; -- out.PutF["<%s>%s", IO.string[directory], IO.string[shortname]]; WHILE RStatsSupport.GetLine[remoteStream, line, out] DO nLines ← nLines + 1; IF checkForType THEN { IF RStatsSupport.SubString[line, "PROGRAM"L] THEN { checkForType ← FALSE; nImpl ← nImpl + 1; }; IF RStatsSupport.SubString[line, "DEFINITIONS"L] THEN { checkForType ← FALSE; nDefs ← nDefs + 1; }; IF RStatsSupport.SubString[line, "MONITOR"L] THEN { checkForType ← FALSE; nImpl ← nImpl + 1; }; }; IF line[line.length - 1] ~= '\n THEN EXIT; -- eof ENDLOOP; g.stat[nSrc] ← g.stat[nSrc] + 1; g.stat[nSrcBytes] ← g.stat[nSrcBytes] + info.size; IF nImpl > 0 AND nDefs > 0 THEN out.Put[IO.string["Warning - counted twice as impl and defs.\n"L]]; IF nImpl > 0 THEN g.stat[nSrcImpl] ← g.stat[nSrcImpl] + 1; IF nDefs > 0 THEN g.stat[nSrcDefn] ← g.stat[nSrcDefn] + 1; g.stat[nSrcLines] ← g.stat[nSrcLines] + nLines; -- out.PutChar['\n]; }; { fres: FQ.Result; targetFileName: STRING ← [100]; vstring: STRING ← IF Subr.Prefix[host, "maxc"L] THEN ";"L ELSE "!"L; [fres: fres, remoteVersion: version] ← FQ.FileQuery[host, directory, shortname, version, createtime, FALSE, tty, targetFileName]; SELECT fres FROM foundCorrectVersion => { sfn: STRING ← [100]; CWF.SWF4[sfn, "<%s>%s%s%u"L, directory, shortname, vstring, @version]; STPSubr.EnumerateForRetrieve[host, sfn, OneFile, tty, TRUE]; }; foundWrongVersion, notFound => out.PutF["%s not found.\n", IO.string[targetFileName]]; ENDCASE => ERROR; }}; -- BCD procedures AnalyzeBcd: PROC[host, directory, shortname: LONG STRING, version: CARDINAL, createtime: LONG CARDINAL, out: IO.Handle, tty: Subr.TTYProcs] = {{ bcd: BcdOps.BcdBase; fh: IFSFile.FileHandle; sfn: Rope.ROPE; mth: BcdOps.MTHandle; g.stat[nBcd] ← g.stat[nBcd] + 1; sfn ← IO.PutFR["<%s>%s", IO.string[directory], IO.string[shortname]]; IF version > 0 THEN sfn ← Rope.Cat[sfn, IO.PutFR["!%d", IO.card[version]]]; -- out.PutF["%s ", IO.rope[sfn]]; fh ← RStatsSupport.LeafOpenWithCreate[host, directory, shortname, version, createtime, out, tty ! Subr.FileError => { out.PutF["Error - cannot open %s\n", IO.rope[sfn]]; GOTO leave; }]; IF fh = NIL THEN GOTO leave; LeafSubr.RemoteMap[g.space, fh, 0]; bcd ← Space.LongPointer[g.space]; IF bcd.definitions THEN g.stat[nBcdDefn] ← g.stat[nBcdDefn] + 1 ELSE IF bcd.nConfigs > 0 THEN g.stat[nBcdConfig] ← g.stat[nBcdConfig] + 1 ELSE { mth ← @LOOPHOLE[bcd + bcd.mtOffset, BcdDefs.Base][FIRST[BcdDefs.MTIndex]]; g.stat[nBcdCodeBytes] ← g.stat[nBcdCodeBytes] + mth.code.length; g.stat[nBcdImpl] ← g.stat[nBcdImpl] + 1; }; Space.Unmap[g.space]; g.stat[nBcdFileBytes] ← g.stat[nBcdFileBytes] + IFSFile.GetLength[fh]; IFSFile.Close[fh]; EXITS leave => NULL; }; -- out.PutChar['\n]; }; -- support procedures SetUpTypeScriptStreams: PROC = { vout: IO.Handle; g.typeScript ← TypeScript.Create[info: [name: "RStats Log Viewer", iconic: FALSE]]; destroyEventRegistration ← ViewerEvents.RegisterEventProc[DestroyProc, destroy]; g.fout ← FileIO.Open["RStats.Log", overwrite]; [g.in, vout] ← ViewerIO.CreateViewerStreams[viewer: g.typeScript, name: "RStats Log Viewer", editedStream: FALSE]; g.out ← IO.CreateDribbleStream[vout, g.fout]; g.tty ← Subr.MakeTTYProcs[g.in, g.out, g.typeScript, MyConfirm]; [] ← CWF.SetWriteProcedure[ViewerTTYProc]; }; -- cannot print anything in this, monitor is locked DestroyProc: ViewerEvents.EventProc = TRUSTED { IF g = NIL OR event ~= destroy OR viewer ~= g.typeScript THEN RETURN; g.out.Flush[]; g.fout.Close[]; -- needed because g.out.Close[] does not close the backing file -- g.out.Close[]; can't close, since this will destroy the viewer g↑ ← []; -- erase all pointers viewer.newVersion ← FALSE; -- so it won't ask to confirm new edits ViewerEvents.UnRegisterEventProc[destroyEventRegistration, destroy]; }; ViewerTTYProc: PROC[ch: CHAR] = { g.out.PutChar[ch]; }; MyConfirm: PROC[in, out: IO.Handle, data: REF ANY, msg: Rope.ROPE, dch: CHAR] RETURNS[CHAR] = { value: ATOM; value ← UserExec.AskUser[msg: msg, viewer: NARROW[data], keyList: LIST[$Yes, $No, $All, $Local, $Quit]]; -- order is important SELECT value FROM $All => RETURN['a]; $Local => RETURN['l]; $No => RETURN['n]; $Quit => RETURN['q]; $Yes => RETURN['y]; ENDCASE => ERROR; }; PrintStat: PROC[out: IO.Handle, st: REF StatArray, choice: Choice] = { out.PutChar['\n]; SELECT choice FROM bcd => { out.PutF["Bcd: %d files, %d defs, %d impls, %d configs,\n\t", IO.int[st[nBcd]], IO.int[st[nBcdDefn]], IO.int[st[nBcdImpl]], IO.int[st[nBcdConfig]]]; out.PutF["%d code bytes, %d file bytes\n", IO.int[st[nBcdCodeBytes]], IO.int[st[nBcdFileBytes]]]; }; df => { out.PutF["DF: %d dffiles\n", IO.int[st[nDF]]]; }; src, detailSrc => { out.PutF["Source: %d files, %d mesa, %d defs, %d impls, %d config,\n\t", IO.int[st[nSrc]], IO.int[st[nSrcMesa]], IO.int[st[nSrcDefn]],IO.int[st[nSrcImpl]], IO.int[st[nSrcConfig]]]; out.PutF["%d lines, %d file bytes\n", IO.int[st[nSrcLines]], IO.int[st[nSrcBytes]]]; }; total => { out.PutF["Total: %d files, %d total bytes, %d bcd files, %d bcd file bytes,\n\t", IO.int[st[nTotalFiles]], IO.int[st[nTotalBytes]], IO.int[st[nBcd]], IO.int[st[nBcdFileBytes]]]; out.PutF["%d src files, %d src file bytes, %d other files, %d other bytes\n", IO.int[st[nSrc]], IO.int[st[nSrcBytes]], IO.int[st[nOtherFiles]], IO.int[st[nOtherBytes]]]; }; init, bcdAnalysis => NULL; ENDCASE => ERROR; FOR s: Stat IN Stat DO g.totalStat[s] ← g.totalStat[s] + g.stat[s]; g.stat[s] ← 0; ENDLOOP; out.PutChar['\n]; }; ResetCtrs: PROC = { FOR s: Stat IN Stat DO g.totalStat[s] ← g.stat[s] ← 0; ENDLOOP; g.dfrec ← NIL; g.lastHost ← g.lastDirectory ← g.lastShortname ← g.lastTopDirectory ← NIL; }; Init: PROC = { Directory.DeleteFile[fileName: "RStats.Log"L ! Directory.Error => CONTINUE]; SetUpTypeScriptStreams[]; -- must be first UserExec.RegisterCommand["RStats.~", Main]; }; Init[]; }.