SpyPrintImpl.mesa
Copyright © 1984 by Xerox Corporation. All rights reserved.
John Maxwell on September 19, 1983 10:01 am
Russ Atkinson on June 20, 1984 11:58:15 am PDT
DIRECTORY
AMBridge USING [TVForGFHReferent],
AMModelPrivate USING [FGIndex, FGIToFirstChar, ProgPCToFGI],
AMTypes USING [TV],
Convert USING [RopeFromCard],
IO USING [card, char, Close, CR, Error, int, Put, PutF, rope, STREAM, time],
Loader USING [BCDBuildTime],
PrincOps USING [GFT, GFTIndex, GlobalFrameHandle],
Process USING [Priority],
ProcessExtras USING [CheckForAbort],
Rope USING [Cat, FromChar, Length, ROPE, Substr],
RTSymbolDefs USING [CallableBodyIndex, SymbolTableBase],
RTSymbolOps USING [AcquireRope, BodyName],
RTSymbols USING [AcquireSTBFromGFH, ReleaseSTB],
RTTypesPrivate USING [GetCBTI, GetEp],
SpyClient USING [ClearBreaks, DataType, InitializeSpy, StartSpy, StopSpy],
SpyOps USING [ProcessRef, Call, Count, DestroyLog, GFHFromGFI, justMe, modules, Procedure, processes, PrintBreaks, ReadLog, wakeups, watching],
ViewerIO USING [CreateViewerStreams];
SpyPrintImpl: PROGRAM
IMPORTS
AMBridge, AMModelPrivate, Convert, IO, Loader, ProcessExtras, Rope, RTSymbolOps, RTSymbols, RTTypesPrivate, SpyClient, SpyOps, ViewerIO
EXPORTS SpyClient =
BEGIN
OPEN IO, SpyOps;
ROPE: TYPE = Rope.ROPE;
Error: SIGNAL = CODE;
*********************************************
printing a tree
*********************************************
procMin, min: Count;
printAll: BOOLEANFALSE;
current: SpyClient.DataType;
DisplayData: PUBLIC PROCEDURE
[cutoff: CARDINAL, herald: ROPE, stream: IO.STREAM, spyOnSpyLog: BOOL] = {
BEGIN
ENABLE {
ABORTED => {stream.Put[rope[" . . . aborted.\n"]]; CONTINUE};
IO.Error => CONTINUE};
localStream: IO.STREAMNIL;
IF stream = NIL THEN
stream ← localStream ← ViewerIO.CreateViewerStreams[
name: "Spy log", backingFile: "///Spy/Spy.log"].out;
current ← SpyOps.watching; -- if the Spy is watching itself
PrintHeader[herald, stream];
IF spyOnSpyLog THEN {
SpyClient.ClearBreaks[];
[] ← SpyClient.InitializeSpy[breakProcess, , TRUE];
SpyClient.StartSpy[]};
SpyOps.ReadLog[stream, SpyOps.watching, spyOnSpyLog];
IF cutoff # 0
THEN {min ← MAX[1, SpyOps.wakeups/100]; procMin ← cutoff*min}
ELSE {min ← procMin ← 1};
PrintTree[stream];
IF localStream # NIL THEN IO.Close[localStream];
END;
SpyOps.DestroyLog[];
IF spyOnSpyLog THEN SpyClient.StopSpy[];
};
PrintHeader: PROCEDURE[herald: ROPE, stream: IO.STREAM] =
BEGIN
stream.Put[rope["\n\n==========================================================\n"]];
stream.Put[rope["Cedar Spy of: "], IO.time[Loader.BCDBuildTime[]], rope[".\n"]];
stream.Put[rope["Executed at: "], IO.time[], rope[".\n"]];
IF herald # NIL THEN stream.Put[rope[herald], char[CR]];
SELECT current FROM
CPU, process, breakProcess => stream.Put[rope["Measuring CPU usage."]];
pagefaults => stream.Put[rope["Measuring page faults."]];
allocations => stream.Put[rope["Measuring allocations."]];
wordsAllocated => stream.Put[rope["Measuring words allocated."]];
userDefined => stream.Put[rope["Measuring user breaks."]];
ENDCASE => ERROR;
stream.Put[char[CR]];
IF current = CPU -- THEN IF ~IntervalTimer.exists
THEN stream.Put[rope["(Waking up on vertical retrace. (80 times a second))\n"]];
ELSE stream.Put[rope["(Waking up using IntervalTimer. (100 times a second.))\n"]];
IF SpyOps.watching IN [process..breakProcess] THEN
stream.Put[rope["Watching process: "], rope[Octal[SpyOps.justMe]], rope[".\n"]];
SpyOps.PrintBreaks[stream];
stream.Put[char[CR]];
END;
PrintTree: PROCEDURE[stream: IO.STREAM] =
BEGIN
assorted: Count ← 0;
procs: LIST OF Procedure ← NIL;
next ← " ";
print the processes
SortProcesses[processes];
stream.Put[
rope["~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"],
rope["Breakdown of interesting processes.\n"],
rope["~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"]];
indention ← NIL;
FOR p: LIST OF ProcessRef ← processes, p.rest DO
IF p = NIL THEN EXIT;
IF p.first.calls < min --AND RTProcess.GetPSBIPageFaults[p.first.psb] = 0
THEN {assorted ← assorted + p.first.calls; LOOP};
PrintProcess[stream, p.first];
ENDLOOP;
IF assorted > 0 THEN {
stream.Put[char[CR], rope["Assorted processes"]];
PrintCount[stream, assorted, 0, SpyOps.wakeups]};
print the procedures
stream.Put[
rope["\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"],
rope["Breakdown of interesting procedures.\n"],
rope["~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n"]];
PrintProcs[stream, FindProcs[NIL]];
IF procMin > 1 THEN stream.Put[
rope["\nThe remaining procedures had less than "], card[procMin], rope[" wakeups.\n"]]
ELSE stream.Put[rope["\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"]];
procs ← NIL;
END;
-- handling processes --
SortProcesses: PROCEDURE[list: LIST OF ProcessRef] =
BEGIN
temp: ProcessRef;
changed: BOOLEAN;
l: LIST OF ProcessRef;
IF list = NIL THEN RETURN;
DO changed ← FALSE;
l ← list;
UNTIL l.rest = NIL DO
IF l.first.calls < l.rest.first.calls THEN {
temp ← l.first;
l.first ← l.rest.first;
l.rest.first ← temp;
changed ← TRUE};
l ← l.rest;
ENDLOOP;
IF ~changed THEN EXIT;
ENDLOOP;
END;
PrintProcess: PROCEDURE[stream: IO.STREAM, process: ProcessRef] =
BEGIN
faults: LONG CARDINAL ← 0;
ProcessExtras.CheckForAbort[];
stream.Put[
char[CR], rope["Process "],
rope[Octal[process.psb]]];
print the level(s)
stream.Put[
rope[" running at priority "],
char['[],
rope[LevelName[process.level[0]]]];
IF process.level[1] # 0 THEN
FOR i:CARDINAL IN [1..4] DO
IF process.level[i] = 0 THEN EXIT;
stream.Put[
rope[", "],
rope[LevelName[process.level[i]]]];
ENDLOOP;
stream.Put[char[']]];
print the faults (if any)
faults ← RTProcess.GetPSBIPageFaults[process.psb];
IF faults > 0 THEN stream.Put[
rope[" ("], int[faults], rope[" page faults)"]];
PrintCount[stream, process.calls, 0, SpyOps.wakeups];
print the children
FOR p: LIST OF Call ← process.sons, p.rest DO
IF p = NIL THEN EXIT;
PrintCall[stream, NIL, p.first];
ENDLOOP;
END;
LevelName: PROCEDURE[l: Process.Priority] RETURNS[ROPE] =
INLINE {RETURN[SELECT l FROM
0 => "monitoring", 3 => "foreground",
1 => "background", 4 => "pagefaultLow",
2 => "normal", 5 => "pagefaultHigh",
ENDCASE => "interrupt"]};
--printing procedures --
EnumerateProcs: PROCEDURE[userProc: PROC[Procedure] RETURNS[BOOLEAN]]
RETURNS[last: Procedure] =
INLINE BEGIN
FOR m: LIST OF Procedure ← modules, m.rest WHILE m # NIL DO
FOR p: LIST OF Call ← m.first.sons, p.rest WHILE p # NIL DO
IF p.first.proc.count = 0 AND p.first.proc.calls = 0 THEN LOOP;
IF userProc[p.first.proc] THEN RETURN[p.first.proc];
ENDLOOP;
ENDLOOP;
RETURN[NIL];
END;
FindProcs: PROCEDURE[container: Procedure] RETURNS[list: LIST OF Procedure ← NIL] =
BEGIN
FindProc: PROCEDURE[next: Procedure] RETURNS[BOOLEAN] = {
IF container = NIL
THEN IF next.container = next
THEN list ← CONS[next, list]
ELSE RETURN[FALSE];
IF next.container = container
THEN IF next.container = next
THEN RETURN[FALSE]
ELSE list ← CONS[next, list];
RETURN[FALSE]};
[] ← EnumerateProcs[FindProc];
IF list # NIL THEN SortProcs[list];
END;
SortProcs: PROCEDURE[list: LIST OF Procedure] =
BEGIN
temp: Procedure;
changed: BOOLEAN;
l: LIST OF Procedure;
xCount, yCount: Count;
IF list = NIL THEN RETURN;
DO changed ← FALSE;
l ← list;
UNTIL l.rest = NIL DO
xCount ← l.first.count + l.first.calls;
yCount ← l.rest.first.count + l.rest.first.calls;
IF xCount < yCount THEN {
temp ← l.first;
l.first ← l.rest.first;
l.rest.first ← temp;
changed ← TRUE};
l ← l.rest;
ENDLOOP;
IF ~changed THEN EXIT;
ENDLOOP;
END;
indention, next: ROPENIL;
PrintProcs: PROCEDURE[stream: IO.STREAM, procs: LIST OF Procedure] =
BEGIN
count: NAT ← 0;
procsToPrint: BOOLEAN;
keepLooping: BOOLEAN;
break: BOOLEANFALSE;
list, last: LIST OF Procedure;
allParentsPrinted: BOOLEAN;
Printable: PROC[procs: LIST OF Procedure] RETURNS[BOOLEAN] = INLINE {
RETURN[procs # NIL AND procs.first.calls + procs.first.count >= procMin]};
WHILE procs # NIL DO
list ← NIL;
find all of the procedures whose parents have already been printed
remove them from 'procs' and add them to 'list'
keepLooping ← TRUE;
WHILE keepLooping DO
last ← NIL;
keepLooping ← FALSE;
FOR l: LIST OF Procedure ← procs, l.rest WHILE l # NIL DO
allParentsPrinted ← TRUE;
FOR f: LIST OF Procedure ← l.first.parents, f.rest WHILE f # NIL DO
IF ~f.first.marked THEN {allParentsPrinted ← FALSE; EXIT};
ENDLOOP;
IF ~allParentsPrinted THEN {last ← l; LOOP};
IF last = NIL THEN procs ← l.rest ELSE last.rest ← l.rest;
IF break THEN count ← count + 1; -- a place to break on
IF Printable[l] -- 'print' procs that won't be printed
THEN list ← CONS[l.first, list]
ELSE {PrintProc[stream, l.first]; keepLooping ← TRUE};
ENDLOOP;
ENDLOOP;
guarantee that we can make progress
IF list = NIL THEN {
IF procs = NIL THEN RETURN;
list ← CONS[procs.first, NIL];
procs ← procs.rest};
print the list
IF list.rest # NIL THEN SortProcs[list];
IF break THEN count ← count + 1; -- a place to break on
procsToPrint ← Printable[procs];
FOR list ← list, list.rest WHILE list # NIL DO
IF Printable[list.rest] THEN next ← "! "
ELSE IF procsToPrint THEN next ← ". " ELSE next ← " ";
PrintProc[stream, list.first];
ENDLOOP;
ENDLOOP;
END;
PrintProc: PROCEDURE[stream: IO.STREAM, proc: Procedure] =
BEGIN
looks: BOOLEANFALSE;
length: LONG INTEGER ← 0;
list: LIST OF Procedure ← NIL;
IF proc = NIL THEN RETURN;
IF proc.marked THEN ERROR ELSE proc.marked ← TRUE;
IF proc.gfi = 0 AND proc.entryPC = 0 THEN RETURN; -- "source" module
IF proc.calls + proc.count >= procMin THEN {
print the procs's name
IF ~proc.named THEN SetName[proc];
stream.Put[rope[indention]];
IF proc.count > 0 AND ~(current IN [CPU..breakProcess]) THEN {
looks ← TRUE; stream.PutF["%l", rope["b"]]};
IF proc.parents # NIL AND proc.parents.rest # NIL THEN {
looks ← TRUE; stream.PutF["%l", rope["i"]]};
stream.Put[rope[proc.name]];
IF looks THEN stream.PutF["%l", rope[" "]];
stream.Put[rope[" ("], card[proc.refs], rope[" refs)"]];
PrintCount[stream, proc.count, proc.calls, SpyOps.wakeups];
print the procedures called by proc
FOR p: LIST OF Call ← proc.sons, p.rest DO
IF p = NIL THEN EXIT;
IF p.first.calls < min THEN LOOP;
PrintCall[stream, proc, p.first];
ENDLOOP;
stream.Put[rope[indention], rope[next], char[CR]]};
print the procedures contained by proc
list ← FindProcs[proc];
IF list = NIL THEN RETURN;
indention ← Rope.Cat[indention, next];
PrintProcs[stream, list];
indention ← Rope.Substr[indention, 0, indention.Length[]-2];
END;
PrintCall: PROCEDURE[stream: IO.STREAM, proc: Procedure, call: Call] =
BEGIN
IF ~call.proc.named THEN SetName[call.proc];
stream.Put[rope[indention], rope[next]];
IF proc # NIL AND proc.symbols THEN
stream.Put[rope["("], card[Source[proc.gfi, call.pc]], rope[") "]];
stream.Put[rope[call.proc.name]];
PrintCount[stream, call.calls, 0, SpyOps.wakeups];
ProcessExtras.CheckForAbort[];
END;
SetName: PROCEDURE[proc: Procedure] =
BEGIN
stb: RTSymbolDefs.SymbolTableBase ← [x[e: NIL]];
BEGIN
ENABLE ANY => {IF stb # [x[e: NIL]] THEN RTSymbols.ReleaseSTB[stb]; CONTINUE};
name: ROPE;
temp: Procedure;
gfh: PrincOps.GlobalFrameHandle;
gfh ← SpyOps.GFHFromGFI[proc.gfi];
stb ← RTSymbols.AcquireSTBFromGFH[gfh ! ANY => CONTINUE];
as long as we have the symbol table, do all of the procedures with this gfi
FOR m: LIST OF Procedure ← modules, m.rest WHILE m # NIL DO
IF m.first.gfi # proc.gfi THEN LOOP;
FOR p: LIST OF Call ← m.first.sons, p.rest WHILE p # NIL DO
IF p.first.proc # proc AND p.first.proc.count + p.first.proc.calls < min THEN LOOP;
IF p.first.proc.named THEN LOOP;
name ← NIL;
temp ← p.first.proc;
IF stb # [x[e: NIL]] THEN { -- get the name from the symbol table
ep: CARDINAL ← RTTypesPrivate.GetEp[[temp.entryPC], gfh, stb].ep;
cbti: RTSymbolDefs.CallableBodyIndex ← RTTypesPrivate.GetCBTI[stb, ep];
name ← RTSymbolOps.AcquireRope[stb, RTSymbolOps.BodyName[stb, cbti]]};
IF name = NIL THEN {temp.symbols ← FALSE; name ← Octal[temp.entryPC]};
temp.name ← Rope.Cat[temp.name, name];
temp.named ← TRUE;
ENDLOOP;
EXIT; ENDLOOP;
IF stb # [x[e: NIL]] THEN RTSymbols.ReleaseSTB[stb];
END;
END;
Sons: PROCEDURE[list: LIST OF Call] RETURNS[BOOLEAN] =
INLINE BEGIN
FOR list ← list, list.rest DO
IF list = NIL THEN EXIT;
IF list.first.calls >= min THEN RETURN[TRUE];
ENDLOOP;
RETURN[FALSE];
END;
Source: PROCEDURE[gfi: PrincOps.GFTIndex, pc: CARDINAL] RETURNS[source: CARDINAL ← 0] =
BEGIN
stb: RTSymbolDefs.SymbolTableBase ← [x[e: NIL]];
{ENABLE {UNWIND => NULL;
ANY => {IF stb # [x[e: NIL]] THEN RTSymbols.ReleaseSTB[stb]; CONTINUE}};
gfh: PrincOps.GlobalFrameHandle = PrincOps.GFT[gfi].framePtr;
gfTV: AMTypes.TV = AMBridge.TVForGFHReferent[gfh];
fgi: AMModelPrivate.FGIndex = AMModelPrivate.ProgPCToFGI[gfTV, [pc]];
stb ← RTSymbols.AcquireSTBFromGFH[gfh];
source ← AMModelPrivate.FGIToFirstChar[stb, fgi];
RTSymbols.ReleaseSTB[stb]};
END;
-- utility procedures --
perCentCR: ROPE = Rope.Cat["%).", Rope.FromChar[CR]];
PrintCount: PROCEDURE[stream: IO.STREAM, c, sonC: Count, total: Count] =
BEGIN
IF c = 0 AND sonC = 0 THEN {stream.Put[char[CR]]; RETURN};
stream.Put[rope[" = "], card[c]];
IF sonC # 0 THEN stream.Put[rope[", "], card[sonC]];
IF total = 0 THEN {stream.Put[char[CR]]; RETURN};
IF (c*100/total) > 1 AND sonC # 0 THEN PerCent[stream, c, total];
PerCent[stream, c+sonC, total];
stream.Put[char['.], char[CR]];
END;
PerCent: PROCEDURE[stream: IO.STREAM, x, y: Count] =
BEGIN
q, r: Count;
IF y = 0 THEN y ← 1;
q ← (x*1000)/y;
r ← q MOD 10;
q ← q/10;
IF q = 0 AND r = 0 AND x # 0 THEN r ← 1; -- to avoid 0.0%
stream.Put[rope[" ("], card[q], char['.]];
stream.Put[card[r], rope["%)"]];
END;
Octal: PROCEDURE[n: CARDINAL] RETURNS[r: ROPE] =
INLINE BEGIN
RETURN[Convert.RopeFromCard[n, 8, TRUE]];
END;
END..
check the pc range
ev ← LOOPHOLE[gfh.code];
entryPC ← ev.entry[ep].initialpc*2;
WITH stb SELECT FROM
t: RTSymbolDefs.SymbolTableBase.x =>
WITH t.e.bb[NARROW[cbti, RTSymbolDefs.CallableBodyIndex.x].e].info SELECT FROM
External=> exitPC ← entryPC + bytes - 1;
ENDCASE=> SIGNAL Error;
t: RTSymbolDefs.SymbolTableBase.y =>
WITH t.e.bb[NARROW[cbti, RTSymbolDefs.CallableBodyIndex.y].e].info SELECT FROM
External=> exitPC ← entryPC + bytes - 1;
ENDCASE=> SIGNAL Error;
ENDCASE => ERROR;
IF entryPC < temp.entryPC OR temp.exitPC < exitPC THEN
name ← Rope.Cat[name, "[", Octal[temp.entryPC], "..", Octal[temp.exitPC], "]"]};
IF name = NIL THEN {temp.symbols ← FALSE; name ← Octal[temp.entryPC]};