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; 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 _ " "; 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]}; 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"]]]; 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 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; }; }; 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]]]]; 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[']]]]; IF faults > 0 THEN stream.Put[ [rope[" ("]], [integer[faults]], [rope[" page faults)"]]]; SpyOps.PrintCount[stream, process.calls, 0, SpyOps.wakeups]; 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"]}; 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; 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; IF list = NIL THEN { IF procs = NIL THEN RETURN; list _ CONS[procs.first, NIL]; procs _ procs.rest}; 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 { 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]; 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]]]}; 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]; 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 { 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]; }; }; Octal: PROC [n: CARDINAL] RETURNS [r: ROPE] = INLINE { RETURN [Convert.RopeFromCard[n, 8, TRUE]]; }; }.. 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]}; šSpyPrintImpl.mesa Copyright c 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 ********************************************* printing a tree ********************************************* print the processes print the procedures print the locations within very active procedures Coalesce places where the Spy saw different pc's, but the actual Tioga reference will be the same. handling processes print the level(s) print the faults (if any) faults _ RTProcess.GetPSBIPageFaults[process.psb]; print the children printing procedures find all of the procedures whose parents have already been printed remove them from 'procs' and add them to 'list' guarantee that we can make progress print the list print the procs's name print the procedures called by proc print the procedures contained by proc as long as we have the symbol table, do all of the procedures with this gfh get the name from the symbol table utility procedures check the pc range Κ6˜codešœ™Kšœ Οmœ7™BKšœ+™+K™0K™(K™'K™.—˜šΟk ˜ Kšœ žœ˜"Kšœžœ(˜˜>Kšœ1˜1Kšœ?˜?—Kšœžœ˜$šžœ žœ ˜Kšœ`˜`KšžœF˜J—K˜K™1˜ Kšœ>˜>Kšœ3˜3Kšœ?˜?—šžœ žœžœžœ˜*Kšœ žœžœ˜,š žœžœžœ!žœžœž˜CK˜KšœΟtœ4˜QKšœB˜Bš žœžœžœžœžœž˜;K™bKš Ÿœžœžœžœžœžœ"˜WKšœžœ˜šžœžœžœž˜0K˜1K˜Kšžœ˜—Kšžœ˜—š žœžœžœžœžœž˜;K˜5Kšžœ˜—Kšžœ˜—Kšœ˜—Kšœ˜—K˜Kšœ™K˜šŸ œžœžœžœ˜2K˜Kšœ žœ˜Kšœžœžœ ˜Kšžœžœžœžœ˜šžœ žœ˜K˜ šžœ žœž˜šžœ$žœ˜,K˜K˜K˜Kšœ žœ˜—K˜ Kšžœ˜—Kšžœ žœžœ˜Kšžœ˜—Kšœ˜K˜—šŸ œžœ žœžœ˜?Kšœžœžœ˜Kšœ˜˜ Kšœ&˜&Kšœ˜—Kšœ™˜ Kšœ ˜ Kšœ˜Kšœ%˜%—šžœž˜Kšžœžœžœž˜Kšžœžœžœ˜"˜ Kšœ˜Kšœ%˜%—Kšžœ˜—Kšœ˜Kšœ™Kšœ2™2šžœ žœ ˜Kšœ:˜:—Kšœ<˜Kšœ žœžœžœ˜&K˜—K˜+Kšœžœ˜ K˜Kšœ5žœžœ˜KKšœK™Kš žœžœžœžœžœž˜;Kšžœžœžœ˜$š žœžœžœžœžœž˜;Kšžœžœ/žœžœ˜SKšžœžœžœ˜ Kšœžœ˜ K˜šžœ žœžœ˜Kšœ"™"Kšœžœ5˜AK˜GK˜F—Kšžœžœžœžœ˜FK˜&Kšœ žœ˜Kšžœ˜—Kšžœ˜Kšžœ˜—Kšžœžœ˜ Kšžœ žœ žœžœ˜DKšœ˜—Kšœ˜K˜—šŸœžœžœžœžœžœžœ˜9šžœž˜Kšžœžœžœžœ˜Kšžœžœžœžœ˜.Kšžœ˜—Kšžœžœ˜Kšœ˜K˜—š Ÿœžœ'žœžœ žœ ˜ZKšœ*žœ˜0šœ˜šžœ˜Kšžœžœ žœžœ˜?Kšœ žœžœžœ˜$Kšœ˜—Kšœžœ"˜2KšœE˜EKšœ(˜(Kšœ1˜1Kšœ˜Kšžœ žœ žœžœ˜BKšœ˜—Kšœ˜—K˜Kšœ™K˜š Ÿœžœžœžœžœžœ˜6Kšžœžœ˜*Kšœ˜—K˜Kšœ˜K˜Kšœ™Kšœžœ ˜K˜#šžœžœž˜˜$šžœžœ1žœž˜NK˜(Kšžœžœ˜——˜$šžœžœ1žœž˜NK˜(Kšžœžœ˜——Kšžœžœ˜—šžœžœžœ˜7KšœP˜P—Kšžœžœžœžœ˜FK˜—…—7 Nά