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: BOOL ← FALSE;
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.STREAM ← NIL;
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: ROPE ← NIL;
PrintProcs:
PROC [stream:
IO.
STREAM, procs:
LIST
OF Procedure] = {
count: NAT ← 0;
procsToPrint: BOOL;
keepLooping: BOOL;
break: BOOL ← FALSE;
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: BOOL ← FALSE;
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]};