SpyPrintImpl.mesa
last modified by: John Maxwell on September 19, 1983 10:01 am
DIRECTORY
AMBridge USING [TVForGFHReferent],
AMModelPrivate USING [FGIndex, FGIToFirstChar, ProgPCToFGI],
AMTypes USING [TV],
Convert USING [RopeFromCard],
IO USING [card, char, 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: BOOLEAN ← FALSE;
current: SpyClient.DataType;
DisplayData:
PUBLIC
PROCEDURE[cutoff:
CARDINAL, herald:
ROPE, stream:
IO.
STREAM, spyOnSpyLog:
BOOL] =
BEGIN
BEGIN
ENABLE {
ABORTED => {stream.Put[rope[" . . . aborted.\n"]]; CONTINUE};
IO.Error => CONTINUE};
IF stream = NIL THEN stream ← ViewerIO.CreateViewerStreams["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];
END;
SpyOps.DestroyLog[];
IF spyOnSpyLog THEN SpyClient.StopSpy[];
END;
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: ROPE ← NIL;
PrintProcs:
PROCEDURE[stream:
IO.
STREAM, procs:
LIST
OF Procedure] =
BEGIN
count: NAT ← 0;
procsToPrint: BOOLEAN;
keepLooping: BOOLEAN;
break: BOOLEAN ← FALSE;
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: BOOLEAN ← FALSE;
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]};