SpyPrintImpl.mesa
Copyright © 1984, 1986 by Xerox Corporation. All rights reserved.
John Maxwell on September 19, 1983 10:01 am
Russ Atkinson (RRA) April 7, 1986 1:13:44 pm PST
McCreight, April 11, 1986 2:29:54 pm PST
Rick Beach, May 5, 1986 11:43:22 am PDT
Eric Nickell, November 25, 1986 4:27:58 pm PST
DIRECTORY
AMBridge USING [TVForGFHReferent],
AMModelPrivate USING [FGIndex, FGIToFirstChar, ProgPCToFGI],
AMTypes USING [TV],
BasicTime USING [Now],
Convert USING [RopeFromCard],
IO USING [Close, Error, Put, PutF, STREAM],
Loader USING [BCDBuildTime],
PrincOps USING [GlobalFrameHandle],
Process USING [CheckForAbort, Priority],
Rope USING [Cat, Length, ROPE, Substr],
RTSymbolDefs USING [CallableBodyIndex, SymbolTableBase],
RTSymbolOps USING [AcquireRope, BodyName],
RTSymbols USING [AcquireSTBFromGFH, ReleaseSTB],
RTTypesPrivate USING [GetCBTI, GetEp],
RuntimeError USING [UNCAUGHT],
SpyClient USING [ClearBreaks, DataType, InitializeSpy, StartSpy, StopSpy],
SpyOps USING [ProcessRef, Call, Count, DestroyLog, justMe, modules, Procedure, processes, PrintBreaks, PrintCount, ReadLog, wakeups, watching],
ViewerIO USING [CreateViewerStreams];
SpyPrintImpl: PROGRAM
IMPORTS AMBridge, AMModelPrivate, BasicTime, Convert, IO, Loader, Process, Rope, RTSymbolOps, RTSymbols, RTTypesPrivate, RuntimeError, SpyClient, SpyOps, ViewerIO
EXPORTS SpyClient = { OPEN SpyOps;
ROPE: TYPE = Rope.ROPE;
Error: SIGNAL = CODE;
*********************************************
printing a tree
*********************************************
procMin, min: Count;
printAll: BOOLFALSE;
current: SpyClient.DataType;
DisplayData: PUBLIC PROC [cutoff: CARDINAL, herald: ROPE, stream: IO.STREAM, spyOnSpyLog: BOOL] = {
{
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];
};
SpyOps.DestroyLog[];
IF spyOnSpyLog THEN SpyClient.StopSpy[];
};
PrintHeader: PROC [herald: ROPE, stream: IO.STREAM] = {
stream.Put[[rope["\n\n==========================================================\n"]]];
stream.Put[[rope["Cedar Spy of: "]], [time[Loader.BCDBuildTime[]]], [rope[".\n"]]];
stream.Put[[rope["Executed at: "]], [time[BasicTime.Now[]]], [rope[".\n"]]];
IF herald # NIL THEN stream.Put[[rope[herald]], [character['\n]]];
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[[character['\n]]];
IF current = CPU -- THEN IF ~IntervalTimer.exists
THEN stream.Put[[rope["(Waking up on vertical retrace. (80 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[[character['\n]]];
};
PrintTree: PROC [stream: IO.STREAM] = {
assorted: Count ← 0;
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[[character['\n]], [rope["Assorted processes"]]];
SpyOps.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 "]], [cardinal[procMin]], [rope[" wakeups.\n"]]]
ELSE stream.Put[[rope["\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"]]];
print the locations within very active procedures
stream.Put[
[rope["\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n"]],
[rope["Breakdown of highly active procedures.\n"]],
[rope["~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\n"]]];
IF (current IN [CPU..breakProcess]) THEN {
procList: LIST OF Procedure ← ActiveProcs[];
FOR each: LIST OF Procedure ← procList, each.rest UNTIL each=NIL DO
proc: Procedure ~ each.first;
stream.PutF[format: "%g (%g)", v1: [rope[proc.name]], v2: [cardinal[proc.refs]]];
SpyOps.PrintCount[stream, proc.count, proc.calls, SpyOps.wakeups];
FOR p: LIST OF Call ← proc.countLocs, p.rest UNTIL p=NIL DO
Coalesce places where the Spy saw different pc's, but the actual Tioga reference will be the same.
Ref: PROC [pc: CARDINAL] RETURNS [ref: INT] ~ {RETURN [Source[gfh: proc.gfh, pc: pc]]};
ref: INT ~ Ref[p.first.pc];
WHILE p.rest#NIL AND ref=Ref[p.rest.first.pc] DO
p.first.calls ← p.first.calls+p.rest.first.calls;
p.rest ← p.rest.rest;
ENDLOOP;
ENDLOOP;
FOR p: LIST OF Call ← proc.countLocs, p.rest UNTIL p=NIL DO
PrintCall[stream: stream, proc: proc, call: p.first];
ENDLOOP;
ENDLOOP;
};
};
handling processes
SortProcesses: PROC [list: LIST OF ProcessRef] = {
temp: ProcessRef;
changed: BOOL;
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;
};
PrintProcess: PROC [stream: IO.STREAM, process: ProcessRef] = {
faults: LONG CARDINAL ← 0;
Process.CheckForAbort[];
stream.Put[
[character['\n]], [rope["Process "]],
[rope[Octal[process.psb]]]];
print the level(s)
stream.Put[
[rope[" running at priority "]],
[character['[]],
[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[[character[']]]];
print the faults (if any)
faults ← RTProcess.GetPSBIPageFaults[process.psb];
IF faults > 0 THEN stream.Put[
[rope[" ("]], [integer[faults]], [rope[" page faults)"]]];
SpyOps.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;
};
LevelName: PROC [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: PROC [userProc: PROC [Procedure] RETURNS [BOOL]] RETURNS [last: Procedure] = INLINE {
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];
};
FindProcs: PROC [container: Procedure] RETURNS [list: LIST OF Procedure ← NIL] = {
FindProc: PROC [next: Procedure] RETURNS [BOOL] = {
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];
};
ActiveProcs: PROC RETURNS [active: LIST OF Procedure ← NIL] ~ {
EachProc: PROC [proc: Procedure] RETURNS [BOOL] ~ {
IF (proc.count*100/SpyOps.wakeups) > 1 THEN active ← CONS[proc, active];
RETURN [FALSE];
};
[] ← EnumerateProcs[EachProc];
IF active#NIL THEN SortProcs[active];
};
SortProcs: PROC [list: LIST OF Procedure] = {
temp: Procedure;
changed: BOOL;
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;
};
indention, next: ROPENIL;
PrintProcs: PROC [stream: IO.STREAM, procs: LIST OF Procedure] = {
count: NAT ← 0;
procsToPrint: BOOL;
keepLooping: BOOL;
break: BOOLFALSE;
list, last: LIST OF Procedure;
allParentsPrinted: BOOL;
Printable: PROC [procs: LIST OF Procedure] RETURNS [BOOL] = 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;
};
PrintProc: PROC [stream: IO.STREAM, proc: Procedure] = {
looks: BOOLFALSE;
length: INT ← 0;
list: LIST OF Procedure ← NIL;
IF proc = NIL THEN RETURN;
IF proc.marked THEN ERROR ELSE proc.marked ← TRUE;
IF proc.gfh = NIL 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]) --not measuring gross things--
OR (current IN [CPU..breakProcess]) AND (proc.count*100/SpyOps.wakeups) > 1 --AND proc.calls # 0-- --when measuring cpu, process, or breaks, embolden only if > 1%--
) 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[" ("]], [cardinal[proc.refs]], [rope[" refs)"]]];
SpyOps.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]], [character['\n]]]};
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];
};
PrintCall: PROC [stream: IO.STREAM, proc: Procedure, call: Call] = {
stream.Put[[rope[indention]], [rope[next]]];
IF proc # NIL AND proc.symbols THEN
stream.Put[[rope["("]], [cardinal[Source[proc.gfh, call.pc]]], [rope[") "]]];
IF call.proc#NIL THEN {
IF ~call.proc.named THEN SetName[call.proc];
stream.Put[[rope[call.proc.name]]];
};
SpyOps.PrintCount[stream, call.calls, 0, SpyOps.wakeups];
Process.CheckForAbort[];
};
SetName: PROC [proc: Procedure] = {
stb: RTSymbolDefs.SymbolTableBase ← [x[e: NIL]];
{
ENABLE {
UNWIND => IF stb # [x[e: NIL]] THEN RTSymbols.ReleaseSTB[stb];
RuntimeError.UNCAUGHT => GO TO finish;
};
gfh: PrincOps.GlobalFrameHandle ← proc.gfh;
name: ROPE;
temp: Procedure;
stb ← RTSymbols.AcquireSTBFromGFH[gfh ! RuntimeError.UNCAUGHT => CONTINUE];
as long as we have the symbol table, do all of the procedures with this gfh
FOR m: LIST OF Procedure ← modules, m.rest WHILE m # NIL DO
IF m.first.gfh # proc.gfh 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;
GO TO finish;
EXITS finish => IF stb # [x[e: NIL]] THEN RTSymbols.ReleaseSTB[stb];
};
};
Sons: PROC [list: LIST OF Call] RETURNS [BOOL] = INLINE {
FOR list ← list, list.rest DO
IF list = NIL THEN EXIT;
IF list.first.calls >= min THEN RETURN [TRUE];
ENDLOOP;
RETURN [FALSE];
};
Source: PROC [gfh: PrincOps.GlobalFrameHandle, pc: CARDINAL] RETURNS [source: INT ← 0] = {
stb: RTSymbolDefs.SymbolTableBase ← [x[e: NIL]];
{
ENABLE {
UNWIND => IF stb # [x[e: NIL]] THEN RTSymbols.ReleaseSTB[stb];
RuntimeError.UNCAUGHT => GO TO oops;
};
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];
EXITS oops => IF stb # [x[e: NIL]] THEN RTSymbols.ReleaseSTB[stb];
};
};
utility procedures
Octal: PROC [n: CARDINAL] RETURNS [r: ROPE] = INLINE {
RETURN [Convert.RopeFromCard[n, 8, TRUE]];
};
}..
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]};