DIRECTORY AMBridge USING [TVForPointerReferent], AMTypes USING [TV, TVType, TypeToName], Convert USING [RopeFromCard], BasicTime USING [GetClockPulses, Pulses, PulsesToMicroseconds, PulsesToSeconds], IO USING [ROS, RopeFromROS, Put, PutChar, PutF, PutFR, STREAM], PrincOps USING [BytePC, CSegPrefix, EntryVectorItem, GFT, GlobalFrameHandle, LastAVSlot, NullPsbHandle, PsbIndex, PsbHandle], PrincOpsUtils USING [PsbHandleToIndex], PrintTV USING [Print], Process USING [Priority], RealOps USING [RoundLI], Rope USING [Cat, FromChar, Equal, ROPE], RTSymbolDefs USING [CallableBodyIndex, SymbolTableBase], RTSymbolOps USING [AcquireRope, BodyName], RTSymbols USING [AcquireSTBFromGFH, ReleaseSTB], RTTypesPrivate USING [GetCBTI, GetEp, GFHToName], RuntimeError USING [UNCAUGHT], SpyLog USING [active, Close, Entry, NextEntry, OpenForRead], SpyOps USING [Call, Count, DataType, LevelData, pageFaults, Procedure, ProcessRef, ProcessRec, ProcRec, runningTime, Stack, stackHeader, stackLength, StackType, wordsAllocated, wordsReclaimed], ViewerIO USING [CreateViewerStreams]; SpyLogReaderImpl: PROGRAM IMPORTS AMBridge, AMTypes, BasicTime, Convert, IO, PrincOpsUtils, PrintTV, RealOps, Rope, RTSymbolOps, RTSymbols, RTTypesPrivate, RuntimeError, SpyLog, SpyOps, ViewerIO EXPORTS SpyOps = { OPEN SpyOps; ROPE: TYPE = Rope.ROPE; STREAM: TYPE = IO.STREAM; Error: SIGNAL = CODE; processes: PUBLIC LIST OF ProcessRef _ NIL; modules: PUBLIC LIST OF Procedure _ NIL; current: SpyOps.DataType; DestroyLog: PUBLIC PROC = { NilLinks: PROC [proc: Procedure] RETURNS [BOOL _ FALSE] = { proc.container _ NIL; proc.parents _ NIL; proc.sons _ NIL}; [] _ EnumerateProcs[NilLinks]; FOR modules _ modules, modules.rest WHILE modules # NIL DO modules.first.container _ NIL; modules.first.parents _ NIL; modules.first.sons _ NIL; ENDLOOP; FOR processes _ processes, processes.rest WHILE processes # NIL DO processes.first.sons _ NIL; ENDLOOP; processes _ NIL; modules _ NIL; SpyLog.Close[]}; ReadLog: PUBLIC PROC [typescript: STREAM, datatype: SpyOps.DataType, spyOnSpyLog: BOOL] = { ignoreBefore: BasicTime.Pulses _ 0; IF typescript = NIL THEN typescript _ ViewerIO.CreateViewerStreams["Spy log"].out; typescript.Put[[rope["(Please wait-- processing log.)\n\n"]]]; current _ datatype; typeName _ ALL[NIL]; SELECT current FROM CPU => className _ NIL; process, breakProcess => { className _ "waiting"; typeName[0] _ "executing"; typeName[1] _ "pagefault"; typeName[2] _ "ML"; typeName[3] _ "CV"; typeName[4] _ "preempted"}; pagefaults => { className _ "pagefault"; typeName[1] _ "data"; typeName[2] _ "code"; typeName[3] _ "xfer"}; allocations, wordsAllocated => { className _ "allocation"; typeName[0] _ "safe"; typeName[1] _ "permanent"; typeName[2] _ "unsafe"}; userDefined => className _ "userBreaks"; ENDCASE => ERROR; typeData _ ALL[0]; IF useTrigger THEN { ignoreBefore _ FindTrigger[]; IF ignoreBefore = LOOPHOLE[0] THEN { typescript.Put[[rope["No trigger encountered.\n\n"]]]; RETURN}; IF triggerExtent > ignoreBefore THEN ignoreBefore _ 0 ELSE ignoreBefore _ ignoreBefore - triggerExtent}; ReadSpyLog[ignoreBefore, spyOnSpyLog]; SetAllContainers[]; PrintStatistics[typescript]; PrintInstructions[typescript]; PrintLogStatistics[typescript]; }; useTrigger: BOOL _ FALSE; triggerExtent: BasicTime.Pulses; whichTrigger: NAT _ 1; SetTrigger: PROC [extent: BasicTime.Pulses, which: NAT _ 1, enable: BOOL _ TRUE] = { useTrigger _ enable; triggerExtent _ extent; whichTrigger _ which; }; FindTrigger: PROC RETURNS [trigger: BasicTime.Pulses] = { count: NAT _ 0; entry: LONG POINTER TO SpyLog.Entry; SpyLog.OpenForRead[]; -- open for reading WHILE TRUE DO entry _ SpyLog.NextEntry[]; IF entry = NIL THEN EXIT; WITH e: entry SELECT FROM endOfLog => EXIT; data => { stack: LONG POINTER TO SpyOps.Stack _ LOOPHOLE[@e.data]; IF stack.type # LAST[SpyOps.StackType] THEN LOOP; count _ count + 1; IF count < whichTrigger THEN LOOP; RETURN [e.timestamp]}; ENDCASE; ENDLOOP; RETURN [0]; }; out: STREAM; wakeups: PUBLIC Count _ 0; -- Number of times that Spy counted something notScheduled: PUBLIC SpyOps.Count _ 0; -- no interesting process sceduled skips: PUBLIC SpyOps.Count _ 0; -- waiting on disk levelData: PUBLIC SpyOps.LevelData _ ALL[0]; typeName: ARRAY StackType OF Rope.ROPE; typeData: ARRAY StackType OF Count; className: Rope.ROPE; processed: Count _ 0; -- Number of stacks in the log overflow: Count _ 0; -- # of samples with stacks >= SpyOps.stackLength error: Count _ 0; -- # of samples with stacks >= SpyOps.stackLength stackDepth: Count _ 0; -- sum of stack depths noCalls, noProcesses, noProcs, noModules: Count _ 0; readLog: BasicTime.Pulses; -- elapsed time to read log recordSpyTime: BOOL _ FALSE; spyTime: ARRAY [0..3] OF RECORD[ count: Count, time: BasicTime.Pulses]; ReadSpyLog: PROC [ignoreBefore: BasicTime.Pulses, spyOnSpyLog: BOOL] = { i: CARDINAL; data: BOOL _ FALSE; dataSize: CARDINAL _ 0; first, last: BasicTime.Pulses _ 0; active: BOOL _ SpyLog.active; t1, delta, dataTime: BasicTime.Pulses; entry: LONG POINTER TO SpyLog.Entry; process: PrincOps.PsbHandle _ PrincOps.NullPsbHandle; Initialize[]; SpyLog.OpenForRead[]; -- open for reading readLog _ 0; t1 _ BasicTime.GetClockPulses[]; WHILE TRUE DO entry _ SpyLog.NextEntry[]; IF entry = NIL THEN EXIT; WITH e: entry SELECT FROM endOfLog => EXIT; nullEntry => LOOP; trace => IF recordSpyTime THEN { IF ~data THEN last _ e.timestamp; IF data THEN { delta _ e.timestamp - last; IF delta > 10000000 THEN {data _ FALSE; RETURN}; SELECT delta FROM <= 10 => i _ 0; <= 100 => i _ 1; <= 1000 => i _ 2; ENDCASE => i _ 3; spyTime[i].count _ spyTime[i].count + 1; spyTime[i].time _ spyTime[i].time + delta}; data _ FALSE} ELSE { IF first = 0 THEN first _ e.timestamp; IF last = 0 THEN delta _ 0 ELSE delta _ e.timestamp - last; IF out = NIL THEN { out _ ViewerIO.CreateViewerStreams["auxilary spy log"].out; out.PutF["%l uSeconds delta data\n", [character['f]]]}; IF e.process # process THEN { out.PutF[" Switch to process: %b\n", [cardinal[PrincOpsUtils.PsbHandleToIndex[e.process]]]]; process _ e.process}; PrintTrace[e.gfh, e.pc, e.timestamp - first, delta]; last _ e.timestamp}; data => { stack: LONG POINTER TO SpyOps.Stack; IF useTrigger AND e.timestamp < ignoreBefore THEN LOOP; IF e.rttype # SpyOps.Stack.CODE THEN { IF first = 0 THEN first _ e.timestamp; IF last = 0 THEN delta _ 0 ELSE delta _ e.timestamp - last; PrintEntry[e.rttype, e.size, @e.data, e.timestamp - first, delta]; last _ e.timestamp; LOOP}; stack _ LOOPHOLE[@e.data]; IF useTrigger AND stack.type = LAST[SpyOps.StackType] THEN EXIT; EnterStack[LOOPHOLE[@e.data], (e.size - SpyOps.stackHeader)/2]; dataSize _ e.size + 4; dataTime _ e.timestamp; data _ TRUE}; ENDCASE => SIGNAL Error; ENDLOOP; readLog _ BasicTime.GetClockPulses[]-t1; stack _ NIL; out _ NIL; }; PrintEntry: PROC [type, words: CARDINAL, data: LONG POINTER, time, delta: BasicTime.Pulses] = { value: AMTypes.TV; IF out = NIL THEN { out _ ViewerIO.CreateViewerStreams["auxilary spy log"].out; out.PutF["%l uSeconds delta data\n", [character['f]]]}; value _ AMBridge.TVForPointerReferent[data, LOOPHOLE[type]]; out.PutF["%9d%9d %g: \n", [cardinal[BasicTime.PulsesToMicroseconds[time]]], [cardinal[BasicTime.PulsesToMicroseconds[delta]]], [rope[AMTypes.TypeToName[AMTypes.TVType[value]]]]]; PrintTV.Print[value, out]; out.PutChar['\n]; }; PrintTrace: PROC [gfh: PrincOps.GlobalFrameHandle, pc: PrincOps.BytePC, time, delta: BasicTime.Pulses] = { name: Rope.ROPE _ GetName[gfh, pc]; out.PutF["%9d%9d %g (gfh: %g, pc: %g)\n", [cardinal[BasicTime.PulsesToMicroseconds[time]]], [cardinal[BasicTime.PulsesToMicroseconds[delta]]], [rope[name]], [cardinal[LOOPHOLE[gfh, CARDINAL]]], [cardinal[pc]]]; }; GetName: PROC [gfh: PrincOps.GlobalFrameHandle, pc: PrincOps.BytePC] RETURNS [name: Rope.ROPE _ NIL] = { proc: Rope.ROPE; stb: RTSymbolDefs.SymbolTableBase _ [x[e: NIL]]; name _ RTTypesPrivate.GFHToName[gfh ! RuntimeError.UNCAUGHT => GO TO badNews]; { ENABLE { RuntimeError.UNCAUGHT => { IF stb # [x[e: NIL]] THEN {RTSymbols.ReleaseSTB[stb]; stb _ [x[e: NIL]]}; CONTINUE}; UNWIND => { IF stb # [x[e: NIL]] THEN {RTSymbols.ReleaseSTB[stb]; stb _ [x[e: NIL]]}; CONTINUE}; }; ep: CARDINAL; cbti: RTSymbolDefs.CallableBodyIndex; stb _ RTSymbols.AcquireSTBFromGFH[gfh]; [ep,] _ RTTypesPrivate.GetEp[[pc], gfh, stb]; cbti _ RTTypesPrivate.GetCBTI[stb, ep]; proc _ RTSymbolOps.AcquireRope[stb, RTSymbolOps.BodyName[stb, cbti]]; RTSymbols.ReleaseSTB[stb]; }; name _ Rope.Cat[name, ".", proc]; EXITS badNews => name _ IO.PutFR["%bB.%bB", [cardinal[LOOPHOLE[gfh, CARDINAL]]], [cardinal[pc]] ]; }; Initialize: PROC = { processes _ NIL; modules _ NIL; stackDepth _ 0; processed _ overflow _ error _ 0; wakeups _ notScheduled _ skips _ 0; noProcesses _ noCalls _ noProcs _ noModules _ 0; spyTime _ ALL[[0, 0]]; levelData _ ALL[0]; stack _ NIL; }; EnumerateProcs: PROC [userProc: PROC [Procedure] RETURNS [BOOL]] RETURNS [last: Procedure] = { 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 userProc[p.first.proc] THEN RETURN [p.first.proc]; ENDLOOP; ENDLOOP; RETURN [NIL]; }; stack: ProcStack; EnterStack: PROC [s: LONG POINTER TO SpyOps.Stack, n: CARDINAL] = { ENABLE Error => {error _ error + 1; CONTINUE}; process: ProcessRef _ NIL; proc, newSon: Procedure _ NIL; processed _ processed + 1; wakeups _ wakeups + s.count; IF s.level = 0 AND n = 0 THEN {SELECT s.type FROM 0 => notScheduled _ notScheduled + s.count; 1 => skips _ skips + s.count; ENDCASE => error _ error + s.count; RETURN}; process _ AddProcess[s.process]; process.calls _ process.calls + s.count; stackDepth _ stackDepth + n; IF process.level[0] # s.level THEN FOR i: CARDINAL IN [0..4] DO IF process.level[i] = s.level THEN EXIT; IF process.level[i] # 0 THEN LOOP; process.level[i] _ s.level; EXIT; ENDLOOP; levelData[s.level] _ levelData[s.level] + s.count; IF s.level = 7 THEN {overflow _ overflow + s.count; RETURN}; IF n = 0 THEN RETURN ELSE n _ n - 1; [process.sons, proc] _ AddSon[process.sons, 0, s.frame[n].gfh, s.frame[n].pc, s.count]; IF proc = NIL THEN {stack _ NIL; RETURN}; IF stack = NIL THEN stack _ NEW[ProcStackRec _ []]; stack.process _ process; AddFrame[stack, proc, 0]; WHILE TRUE DO IF n = 0 THEN EXIT ELSE n _ n - 1; proc.calls _ proc.calls + s.count; [proc.sons, newSon] _ AddSon[proc.sons, s.frame[n+1].pc, s.frame[n].gfh, s.frame[n].pc, s.count]; proc _ newSon; IF proc = NIL THEN EXIT; AddFrame[stack, proc, s.frame[n+1].pc]; IF Recursive[stack] THEN Undo[stack, s.count]; ENDLOOP; IF proc # NIL THEN { proc.count _ proc.count + s.count; IF s.type # 0 THEN { [proc.sons, newSon] _ AddSon[proc.sons, s.frame[0].pc, NIL, s.type, s.count]; IF newSon # NIL THEN newSon.count _ newSon.count + s.count; }; }; typeData[s.type] _ typeData[s.type] + s.count; stack.index _ 0; }; AddProcess: PROC [psb: PrincOps.PsbIndex] RETURNS [process: ProcessRef _ NIL] = { list: LIST OF ProcessRef _ processes; IF list = NIL OR psb < list.first.psb THEN { process _ NewProcess[psb]; processes _ CONS[process, processes]; RETURN [process]}; WHILE list # NIL DO process _ list.first; IF process.psb = psb THEN RETURN [process]; IF list.rest = NIL OR psb < list.rest.first.psb THEN { process _ NewProcess[psb]; list.rest _ CONS[process, list.rest]; RETURN [process]}; list _ list.rest; ENDLOOP; ERROR; }; NewProcess: PROC [psb: PrincOps.PsbIndex] RETURNS [process: ProcessRef] = { process _ NEW[ProcessRec _ []]; noProcesses _ noProcesses + 1; process.psb _ psb; }; AddSon: PROC [list: LIST OF Call, from: CARDINAL, gfh: PrincOps.GlobalFrameHandle, pc, count: Count] RETURNS [newList: LIST OF Call, procedure: Procedure] = { procedure _ NIL; newList _ list; IF list = NIL OR from < list.first.pc THEN { procedure _ AddProcedure[gfh, pc]; IF procedure = NIL THEN RETURN [NIL, NIL]; newList _ CONS[[from, count, procedure], list]; noCalls _ noCalls + 1; RETURN [newList, procedure]; }; WHILE list # NIL DO procedure _ list.first.proc; IF list.first.pc = from AND Equal[gfh, pc, procedure] THEN { IF list.first.calls = 0 THEN list.first.proc.refs _ list.first.proc.refs + 1; list.first.calls _ list.first.calls + count; RETURN [newList, procedure]}; IF list.rest = NIL OR from < list.rest.first.pc THEN { procedure _ AddProcedure[gfh, pc]; IF procedure = NIL THEN RETURN [NIL, NIL]; list.rest _ CONS[[from, count, procedure], list.rest]; noCalls _ noCalls + 1; RETURN [newList, procedure]}; list _ list.rest; ENDLOOP; ERROR; }; RemoveSon: PROC [proc: Procedure, pc: CARDINAL, list: LIST OF Call, count: Count] RETURNS [newList: LIST OF Call] = { prior: LIST OF Call; FOR s: LIST OF Call _ list, s.rest DO IF s = NIL THEN SIGNAL Error; -- where is it? IF s.first.proc # proc OR s.first.pc # pc THEN {prior _ s; LOOP}; IF s.first.calls = 0 THEN RETURN [list]; s.first.calls _ s.first.calls - count; IF s.first.calls = 0 THEN s.first.proc.refs _ s.first.proc.refs - 1; RETURN [list]; ENDLOOP; }; GreaterThan: PROC [gfh: PrincOps.GlobalFrameHandle, pc: CARDINAL, proc: Procedure] RETURNS [BOOL] = INLINE { IF gfh # proc.gfh THEN RETURN [LOOPHOLE[gfh, CARDINAL] > LOOPHOLE[proc.gfh, CARDINAL]]; RETURN [pc > proc.exitPC]; }; Equal: PROC [gfh: PrincOps.GlobalFrameHandle, pc: INT, proc: Procedure] RETURNS [BOOL] = INLINE { entryPC, exitPC: INT; IF proc = NIL THEN RETURN [TRUE]; -- source IF gfh # proc.gfh THEN RETURN [FALSE]; IF pc < (entryPC _ proc.entryPC) THEN RETURN [FALSE]; IF pc > (exitPC _ proc.exitPC) THEN RETURN [FALSE]; RETURN [TRUE]; }; AddProcedure: PROC [gfh: PrincOps.GlobalFrameHandle, pc: CARDINAL] RETURNS [procedure: Procedure _ NIL] = { module: Procedure; list: LIST OF Call; module _ AddModule[gfh]; IF module = NIL THEN RETURN; list _ module.sons; IF list = NIL OR GreaterThan[gfh, pc, list.first.proc] THEN { procedure _ NewProcedure[module, pc]; IF procedure = NIL THEN SIGNAL Error; procedure.refs _ 1; module.sons _ CONS[[0, 0, procedure], list]; noCalls _ noCalls + 1; RETURN [procedure]}; WHILE list # NIL DO procedure _ list.first.proc; IF Equal[gfh, pc, procedure] THEN { procedure.refs _ procedure.refs + 1; RETURN [procedure]}; IF list.rest = NIL OR GreaterThan[gfh, pc, list.rest.first.proc] THEN { procedure _ NewProcedure[module, pc]; IF procedure = NIL THEN SIGNAL Error; procedure.refs _ 1; list.rest _ CONS[[0, 0, procedure], list.rest]; noCalls _ noCalls + 1; RETURN [procedure]}; list _ list.rest; ENDLOOP; ERROR; }; AddModule: PROC [gfh: PrincOps.GlobalFrameHandle] RETURNS [module: Procedure _ NIL] = { list: LIST OF Procedure _ modules; ValidateGFH[gfh ! RuntimeError.UNCAUGHT => GO TO badGuy]; IF list = NIL OR LOOPHOLE[gfh, CARDINAL] > LOOPHOLE[list.first.gfh, CARDINAL] THEN { module _ NewModule[gfh]; modules _ CONS[module, list]; RETURN [module]}; WHILE list # NIL DO module _ list.first; IF gfh = module.gfh THEN RETURN [module]; IF list.rest = NIL OR LOOPHOLE[gfh, CARDINAL] > LOOPHOLE[list.rest.first.gfh, CARDINAL] THEN { module _ NewModule[gfh]; list.rest _ CONS[module, list.rest]; RETURN [module]}; list _ list.rest; ENDLOOP; ERROR; EXITS badGuy => RETURN [NIL]; }; ValidateGFH: PROC [gfh: PrincOps.GlobalFrameHandle] = { IF gfh # PrincOps.GFT[gfh.gfi].framePtr THEN ERROR; }; NewModule: PROC [gfh: PrincOps.GlobalFrameHandle] RETURNS [module: Procedure] = { module _ NEW[ProcRec _ []]; noModules _ noModules + 1; module.gfh _ gfh; module.name _ (IF gfh = NIL THEN className ELSE RTTypesPrivate.GFHToName[gfh]); }; NewProcedure: PROC [module: Procedure, pc: CARDINAL] RETURNS [procedure: Procedure _ NIL] = { name: ROPE; entryPC, exitPC: CARDINAL _ pc; IF module.gfh # NIL THEN [entryPC, exitPC] _ PcRange[module.gfh, pc]; procedure _ NEW[ProcRec _ []]; noProcs _ noProcs + 1; procedure.gfh _ module.gfh; procedure.entryPC _ entryPC; procedure.exitPC _ exitPC; IF module.gfh = NIL THEN name _ typeName[pc]; IF module.gfh = NIL AND name = NIL THEN { stream: STREAM _ IO.ROS[]; stream.Put[[rope["type"]], [cardinal[pc]]]; typeName[pc] _ name _ IO.RopeFromROS[stream]}; procedure.name _ Rope.Cat[module.name, Rope.FromChar['.], name]; procedure.named _ name # NIL; }; PcRange: PROC [gfh: PrincOps.GlobalFrameHandle, pc: CARDINAL] RETURNS [entry, exit: CARDINAL] = { procPC, min: CARDINAL _ pc; ev: LONG POINTER TO PrincOps.CSegPrefix; entry _ 0; exit _ min _ 177777B; ev _ LOOPHOLE[gfh.code]; FOR i: CARDINAL IN [0..ev.header.info.ngfi*32) DO IF ev.entry[i].initialpc > 77777B THEN EXIT; IF ev.entry[i].info.framesize > PrincOps.LastAVSlot THEN EXIT; min _ MIN[min, ev.entry[i].initialpc/SIZE[PrincOps.EntryVectorItem]]; IF i+1 >= min THEN EXIT; procPC _ ev.entry[i].initialpc*2; IF entry < procPC AND procPC <= pc THEN entry _ procPC; IF pc < procPC AND procPC < exit THEN exit _ procPC; ENDLOOP; exit _ exit-1; }; ProcStack: TYPE = REF ProcStackRec; ProcStackRec: TYPE = RECORD[ process: ProcessRef _ NIL, index: CARDINAL _ 0, frame: ARRAY [0..SpyOps.stackLength) OF RECORD[ proc: Procedure _ NIL, pc: CARDINAL _ 0]]; AddFrame: PROC [stack: ProcStack, proc: Procedure, pc: CARDINAL] = INLINE { stack.frame[stack.index] _ [proc, pc]; stack.index _ stack.index + 1}; Recursive: PROC [stack: ProcStack] RETURNS [BOOL] = INLINE { IF stack.index = 0 THEN RETURN [FALSE]; FOR i: CARDINAL DECREASING IN [0..stack.index-1) DO IF stack.frame[i].proc = stack.frame[stack.index-1].proc THEN RETURN [TRUE]; ENDLOOP; RETURN [FALSE]; }; Undo: PROC [stack: ProcStack, count: Count] = { first, proc: Procedure; IF stack.index = 0 THEN RETURN; first _ stack.frame[stack.index-1].proc; FOR i: CARDINAL DECREASING IN [0..stack.index-1) DO proc _ stack.frame[i].proc; proc.sons _ RemoveSon[stack.frame[i+1].proc, stack.frame[i+1].pc, proc.sons, count]; proc.calls _ proc.calls - count; IF proc = first THEN {stack.index _ i + 1; RETURN}; ENDLOOP; ERROR; }; ClearContainers: PROC = { 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 p.first.proc.container _ NIL; ENDLOOP; ENDLOOP; }; SetAllContainers: PROC = { SetAllParents[]; FOR a: LIST OF ProcessRef _ processes, a.rest WHILE a # NIL DO FOR s: LIST OF Call _ a.first.sons, s.rest WHILE s # NIL DO s.first.proc.container _ s.first.proc; ENDLOOP; ENDLOOP; FOR a: LIST OF ProcessRef _ processes, a.rest WHILE a # NIL DO FOR p: LIST OF Call _ a.first.sons, p.rest WHILE p # NIL DO SetContainers[p.first.proc.sons]; ENDLOOP; ENDLOOP; 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.container # NIL THEN LOOP; p.first.proc.container _ Container[p.first.proc]; IF p.first.proc.container = NIL THEN p.first.proc.container _ p.first.proc; ENDLOOP; ENDLOOP; }; SetAllParents: PROC = { proc: Procedure; found: BOOL; 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 proc _ p.first.proc; -- for each procedure . . . FOR s: LIST OF Call _ proc.sons, s.rest WHILE s # NIL DO found _ FALSE; IF s.first.calls = 0 THEN LOOP; FOR f: LIST OF Procedure _ s.first.proc.parents, f.rest WHILE f # NIL DO IF f.first = proc THEN {found _ TRUE; EXIT}; ENDLOOP; IF ~found THEN s.first.proc.parents _ CONS[proc, s.first.proc.parents]; ENDLOOP; ENDLOOP; ENDLOOP; }; SetContainers: PROC [sons: LIST OF Call] = { FOR s: LIST OF Call _ sons, s.rest DO IF s = NIL THEN EXIT; IF s.first.proc.container # NIL THEN LOOP; s.first.proc.container _ Container[s.first.proc]; IF s.first.proc.container = NIL THEN LOOP; SetContainers[s.first.proc.sons]; ENDLOOP; }; Container: PROC [proc: Procedure] RETURNS [c: Procedure _ NIL] = { proc.marked _ TRUE; FOR f: LIST OF Procedure _ proc.parents, f.rest WHILE f # NIL DO IF f.first.marked THEN LOOP; -- already traversed it IF f.first.container = NIL THEN f.first.container _ Container[f.first]; IF f.first.container = NIL THEN LOOP; IF c = NIL THEN {c _ f.first; LOOP}; c _ Intersection[c, f.first]; IF c = NIL THEN {proc.marked _ FALSE; RETURN [proc]}; -- no container ENDLOOP; proc.marked _ FALSE; }; Intersection: PROC [a, b: Procedure] RETURNS [Procedure] = { DO IF a = b THEN RETURN [a]; FOR c: Procedure _ a, c.container DO IF c.container = b THEN RETURN [b]; IF c.container = c THEN EXIT; ENDLOOP; FOR c: Procedure _ b, c.container DO IF c.container = a THEN RETURN [a]; IF c.container = c THEN EXIT; ENDLOOP; IF a = a.container OR b = b.container THEN RETURN [NIL]; a _ a.container; b _ b.container; ENDLOOP; }; PrintStatistics: PROC [stream: STREAM] = { seconds: INTEGER; IF wakeups = 0 THEN RETURN; seconds _ MAX[RealOps.RoundLI[BasicTime.PulsesToSeconds[SpyOps.runningTime]], 1]; stream.PutF["Spy ran for %g minutes %g seconds.\n", [cardinal[seconds/60]], [cardinal[seconds MOD 60]]]; stream.Put[[rope["Total wakeups = "]], [cardinal[wakeups]]]; stream.Put[[rope[" ("]], [cardinal[wakeups/seconds]], [rope[" per second).\n"]]]; IF current # pagefaults THEN { stream.Put[[rope["Total page faults = "]], [cardinal[SpyOps.pageFaults]]]; stream.Put[[rope[" ("]], [cardinal[SpyOps.pageFaults/seconds]], [rope[" per second).\n"]]]}; stream.Put[[rope["Total words allocated = "]], [cardinal[SpyOps.wordsAllocated]]]; stream.Put[[rope[" ("]], [cardinal[SpyOps.wordsAllocated/seconds]], [rope[" per second).\n"]]]; stream.Put[[rope["Total words reclaimed = "]], [cardinal[SpyOps.wordsReclaimed]]]; stream.Put[[rope[" ("]], [cardinal[SpyOps.wordsReclaimed/seconds]], [rope[" per second).\n"]]]; stream.Put[[rope["\nScheduled Process-Priority Summary:\n"]]]; IF notScheduled # 0 THEN { stream.Put[[rope[" processor idle"]]]; PrintCount[stream, notScheduled, 0, wakeups]}; IF skips # 0 THEN { stream.Put[[rope[" processor waiting on disk"]]]; PrintCount[stream, skips, 0, wakeups]}; FOR i: Process.Priority IN Process.Priority DO IF levelData[i] = 0 THEN LOOP; stream.Put[[rope[" priority "]], [rope[LevelName[i]]]]; PrintCount[stream, levelData[i], 0, wakeups]; ENDLOOP; stream.Put[[character['\n]]]; IF className # NIL THEN { header: Rope.ROPE; headerPrinted: BOOL _ FALSE; SELECT current FROM pagefaults => header _ "Breakdown By Type Of Pagefault:\n"; allocations, wordsAllocated => header _ "Breakdown By Type Of Allocation:\n"; ENDCASE => header _ "Breakdown By Type Of Action:\n"; FOR i: StackType IN StackType DO IF typeName[i] = NIL OR typeData[i] = 0 THEN LOOP; IF ~headerPrinted THEN { stream.Put[[rope[header]]]; headerPrinted _ TRUE}; IF current IN [process..breakProcess] AND i # 0 THEN stream.Put[[rope[" waiting "]], [rope[typeName[i]]]] ELSE stream.Put[[rope[" "]], IF typeName[i] # NIL THEN [rope[typeName[i]]] ELSE [integer[i]]]; PrintCount[stream, typeData[i], 0, wakeups]; ENDLOOP; stream.Put[[character['\n]]]}; }; PrintInstructions: PROC [stream: STREAM] = { stream.Put[[rope["Instructions (see SpyDoc.tioga for more information):"]], [character['\n]]]; stream.Put[[character['\t]], [rope["Indentation is used to show containment."]], [character['\n]]]; stream.Put[[character['\t]], [rope["Periods are used to keep track of procedures at the same level of indentation."]], [character['\n]]]; stream.Put[[character['\t]], [rope["Exclamation points (!) are used to group together sets of disjoint procedures."]], [character['\n]]]; stream.Put[[character['\t]], [rope["Italics are used to indicate procedures with more than one parent."]], [character['\n]]]; stream.Put[[character['\t]], [rope["Bold is used to indicate procedures which actually had allocations or page faults."]], [character['\n]]]; stream.Put[[character['\t]], [rope["Impl.Proc = x, y (o%) (z%):"]], [character['\n]]]; stream.Put[[character['\t]], [rope[" x = counts in Impl.Proc."]], [character['\n]]]; stream.Put[[character['\t]], [rope[" y = counts in procedures called from Impl.Proc."]], [character['\n]]]; stream.Put[[character['\t]], [rope[" o = x/ total % (only displayed if > 1%)."]], [character['\n]]]; stream.Put[[character['\t]], [rope[" z = (x + y)/ total = % time IN THE CALL STACK."]], [character['\n]]]; stream.Put[[character['\n]]]; }; PrintLogStatistics: PROC [stream: STREAM] = { allocated: Count; stream.Put[[rope["Statistics on execution of Cedar Spy:"]], [character['\n]]]; IF spyTime[0].count > 0 THEN { stream.Put[ [character['\t]], [cardinal[spyTime[0].count]], [rope[" spy samples averaged "]]]; PrintTime[stream, BasicTime.PulsesToMicroseconds[spyTime[0].time]/spyTime[0].count]}; IF spyTime[1].count > 0 THEN { stream.Put[ [character['\t]], [cardinal[spyTime[1].count]], [rope[" spy samples averaged "]]]; PrintTime[stream, BasicTime.PulsesToMicroseconds[spyTime[1].time]/spyTime[1].count]}; IF spyTime[2].count > 0 THEN { stream.Put[ [character['\t]], [cardinal[spyTime[2].count]], [rope[" spy samples averaged "]]]; PrintTime[stream, BasicTime.PulsesToMicroseconds[spyTime[2].time]/spyTime[2].count]}; IF spyTime[3].count > 0 THEN { stream.Put[ [character['\t]], [cardinal[spyTime[3].count]], [rope[" spy samples averaged "]]]; PrintTime[stream, BasicTime.PulsesToMicroseconds[spyTime[3].time]/spyTime[3].count]}; IF readLog IN (0..10000000] THEN {-- to avoid wrap around stream.Put[ [character['\t]], [rope["Processed log in "]]]; PrintTime[stream, BasicTime.PulsesToMicroseconds[readLog]]}; stream.Put[ [rope["\tTotal samples read from log = "]], [cardinal[processed]], [rope[".\n"]]]; IF overflow > 0 THEN { stream.Put[[character['\t]], [cardinal[overflow]], [rope[" SAMPLES WITH STACKS OF >= "]] ]; stream.Put[ [rope[Convert.RopeFromCard[from: SpyOps.stackLength, showRadix: FALSE]]], [rope[" FRAMES!\n"]] ]; }; IF error > 0 THEN stream.Put[[character['\t]], [cardinal[error]], [rope[" SAMPLES WERE CUT SHORT BECAUSE OF ERRORS!\n"]]]; IF processed > 0 THEN stream.Put[[rope["\tAverage stack depth = "]], [cardinal[stackDepth/processed]], [rope[".\n"]]]; stream.Put[[rope["\tNo. modules allocated = "]], [cardinal[noModules]], [rope[".\n"]]]; stream.Put[[rope["\tNo. procedures allocated = "]], [cardinal[noProcs]], [rope[".\n"]]]; allocated _ noProcesses*SIZE[ProcessRec] + noCalls*SIZE[Call] + noModules*SIZE[ProcRec] + noProcs*SIZE[ProcRec]; stream.Put[[rope["\tTotal words allocated = "]], [cardinal[allocated]], [rope[".\n"]]]; stream.Put[[character['\n]]]; }; 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"]}; PrintCount: PUBLIC PROC [stream: STREAM, c, sonC: Count, total: Count] = { IF c = 0 AND sonC = 0 THEN {stream.Put[[character['\n]]]; RETURN}; stream.Put[[rope[" = "]], [cardinal[c]]]; IF sonC # 0 THEN stream.Put[[rope[", "]], [cardinal[sonC]]]; IF total = 0 THEN {stream.Put[[character['\n]]]; RETURN}; IF (c*100/total) > 1 AND sonC # 0 THEN PerCent[stream, c, total]; PerCent[stream, c+sonC, total]; stream.Put[[character['.]], [character['\n]]]; }; PerCent: PUBLIC PROC [stream: STREAM, x, y: Count] = { q, r: Count; sign: INT _ 1; IF y = 0 THEN y _ 1; q _ RealOps.RoundLI[(REAL[x]*1000.0)/REAL[y]]; IF q < 0 THEN {sign _ -1; q _ -q}; r _ IF q = 0 AND x # 0 THEN 1 ELSE (q MOD 10); -- to avoid 0.0% q _ (q/10); stream.Put[[rope[" ("]], [integer[sign*q]], [character['.]]]; stream.Put[[cardinal[r]], [rope["%)"]]]; }; PrintTime: PROC [stream: STREAM, ticks: LONG CARDINAL] = { secs,mSecs,uSecs: LONG CARDINAL; uSecs _ ticks; secs _ uSecs/1000000; uSecs _ uSecs - secs*1000000; mSecs _ uSecs/1000; uSecs _ uSecs - mSecs*1000; IF secs # 0 OR mSecs > 10 THEN stream.PutF["%g.%g seconds.", [cardinal[secs]], [cardinal[mSecs]]] ELSE stream.PutF["%g.%g milliseconds.", [cardinal[mSecs]], [cardinal[uSecs]]]; }; Find: PROC [name: ROPE] RETURNS [Procedure] = { list: LIST OF Procedure _ modules; FOR m: LIST OF Procedure _ list, m.rest DO IF m = NIL THEN EXIT; IF Rope.Equal[name, m.first.name] THEN RETURN [m.first]; IF list # modules THEN LOOP; FOR p: LIST OF Call _ m.first.sons, p.rest DO IF p = NIL THEN EXIT; IF Rope.Equal[name, p.first.proc.name] THEN RETURN [p.first.proc]; ENDLOOP; ENDLOOP; RETURN [NIL]; }; }.. CountStorage: PROC RETURNS [process, mods, procs, sons: INTEGER, total:LONG INTEGER] = { process _ mods _ procs _ sons _ 0; FOR m: LIST OF Procedure _ modules, m.rest DO IF m = NIL THEN EXIT; mods _ mods + 1; FOR p: LIST OF Call _ m.first.sons, p.rest DO IF p = NIL THEN EXIT; sons _ sons + 1; procs _ procs + 1; FOR s: LIST OF Call _ p.first.proc.sons, s.rest DO IF s = NIL THEN EXIT; sons _ sons + 1; ENDLOOP; ENDLOOP; ENDLOOP; FOR p: LIST OF ProcessRef _ processes, p.rest DO IF p = NIL THEN EXIT; process _ process + 1; FOR s: LIST OF Call _ p.first.sons, s.rest DO IF s = NIL THEN EXIT; sons _ sons + 1; ENDLOOP; ENDLOOP; total _ process*SIZE[ProcessRec] + (mods + procs)*SIZE[ProcRec] + sons*SIZE[Call]; }; FSpyLogReaderImpl.mesa Copyright c 1985, 1986 by Xerox Corporation. All rights reserved. Maxwell on December 14, 1983 2:25 pm Russ Atkinson (RRA) April 7, 1986 12:55:53 pm PST Pier, December 12, 1985 10:51:00 am PST McCreight, April 11, 1986 2:30:38 pm PST global variables and types procedures listed in top down order ********************************************* reading the section of the log just before a trigger ********************************************* ********************************************* building a call tree from the trace log ********************************************* public statistics private statistics IF UserInput.userAbort THEN EXIT; IF active THEN SpyLog.Open[TRUE]; IF UserInput.userAbort THEN ERROR SpyOps.UserAborted; tree management adding a stack to the call tree INVARIANT: exactly n proc.calls or proc.count should be incremented. add the first frame to the appropriate process list [level: 7, n: 0] is the code for an overflow are we at the end of the stack? add the current frame to the current node add in the leaf counts treat as a call even tho it's a count adding to the list of processes -- should we add it to the beginning of the list? is this the process we want? should the process follow this one? adding/deleting procedures from a LIST OF Call -- INVARIANT: the lists remain sorted INVARIANT: exactly one LIST OF Call.calls will be incremented should we add it to the beginning of the list? is this the procedure we want? should the procedure follow this one? INVARIANT: the lists remain sorted INVARIANT: exactly one LIST OF Call.calls will be decremented IF s.first.calls > 0 THEN RETURN[list]; s.first.proc.refs _ s.first.proc.refs - 1; IF s = list THEN RETURN[list.rest]; prior.rest _ s.rest; adding modules and procedures to the modules list INVARIANT: the lists remain sorted INVARIANT: exactly one ref will be incremented should we add it to the beginning of the list? is this the procedure we want? should the procedure follow this one? INVARIANT: the lists remain sorted should we add it to the beginning of the list? is this the module we want? should the module follow this one? heuristics to see if we are at the end of the entry vector determine the entry and exit PCs. handling marked stacks ********************************************* determining the precedence relationships ********************************************* set the root containers chase down the trees catch all of the ones we missed . . . add self to son's parent list determine the intersection of the procedures that references proc ********************************************* some printing ********************************************* print general statistics print how the processor was used print breakdown by types: ********************************************* debugging facilities ********************************************* Κ$x˜codešœ™Kšœ Οmœ7™BKšœ$™$K™1K™'K™(—˜šΟk ˜ Kšœ žœ˜&Kšœžœžœ˜'Kšœžœ˜Kšœ žœA˜PKšžœžœžœ*žœ˜?Kšœ žœ'žœE˜}Kšœžœ˜'Kšœžœ ˜Kšœžœ ˜Kšœžœ ˜Kšœžœžœ˜(Kšœ žœ&˜8Kšœ žœ˜*Kšœ žœ!˜0Kšœžœ˜1Kšœ žœžœ˜Kšœžœ0˜K˜Kšœ žœžœ˜šžœ ž˜Kšžœžœ˜šœ˜K˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜—šœ˜K˜Kšœ˜Kšœ˜Kšœ˜—šœ ˜ K˜Kšœ˜Kšœ˜Kšœ˜—Kšœ(˜(Kšžœžœ˜—Kšœ žœ˜šžœ žœ˜K˜šžœžœžœ˜$Kšœ7žœ˜?—šžœ˜ Kšžœ˜Kšžœ0˜4——Kšœ&˜&K˜K˜K˜K˜Kšœ˜—K˜Kšœ-™-Kšœ5™5Kšœ-™-K˜Kšœ žœžœ˜K˜ Kšœžœ˜K˜š Ÿ œžœ#žœžœžœ˜TKšœ˜Kšœ˜Kšœ˜Kšœ˜K˜—šŸ œžœžœ ˜9Kšœžœ˜Kšœžœžœžœ˜$KšœΟc˜)šžœžœž˜ Kšœ˜Kšžœ žœžœžœ˜šžœ žœž˜Kšœ žœ˜˜ Kš œžœžœžœžœ ˜8Kšžœžœžœžœ˜1K˜Kšžœžœžœ˜"Kšžœ˜—Kšžœ˜—Kšžœ˜—Kšžœ˜ Kšœ˜—K˜Kšœ-™-Kšœ(™(Kšœ-™-Kšœžœ˜ K˜Kšœ™Kšœ žœ  -˜HKšœžœ "˜IKšœžœ ˜2Kšœ žœžœ˜,Kšœ žœ žœžœ˜(Kšœ žœ žœ˜#Kšœžœ˜K˜Kšœ™Kšœ ˜4Kšœ 1˜FKšœ 1˜CKšœ ˜-K˜4Kšœ ˜6K˜Kšœžœžœ˜šœ žœžœžœ˜ K˜K˜K˜—K˜šŸ œžœ/žœ˜HKšœžœ˜ Kšœžœžœ˜Kšœ žœ˜K˜"Kšœžœ˜K˜&Kšœžœžœžœ˜$Kšœ5˜5K˜ Kšœ ˜)K˜ K˜ šžœžœž˜ Kšœ!™!K˜Kšžœ žœžœžœ˜šžœ žœž˜Kšœ žœ˜Kšœ žœ˜šœ žœžœ˜ Kšžœžœ˜!šžœžœ˜K˜Kšžœžœ žœžœ˜0šžœžœ˜K˜!Kšœžœ ˜#—K˜(K˜+Kšœžœ˜ —šžœ˜Kšžœ žœ˜&Kšžœ žœ žœ˜;šžœžœžœ˜Kšœ;˜;K˜=—šžœžœ˜Kšœq˜qK˜—K˜4K˜——˜ Kšœžœžœžœ˜$Kšžœ žœžœžœ˜7šžœžœžœ˜&Kšžœ žœ˜&Kšžœ žœ žœ˜;K˜CKšœžœ˜—Kšœžœ ˜Kš žœ žœžœžœžœ˜@Kšœ žœ,˜?K˜K˜Kšœžœ˜ —Kšžœžœ˜—Kšžœ˜—K˜(Kšœ!™!Kšœ5™5Kšœžœ˜ Kšœžœ˜ Kšœ˜K˜—š Ÿ œžœžœžœžœ$˜_Kšœžœ˜šžœžœžœ˜Kšœ;˜;K˜<—Kšœ,žœ˜<˜K˜2K˜3K˜3—Kšœ˜Kšœ˜Kšœ˜K˜—šŸ œžœY˜jKšœ žœ˜#˜,K˜2K˜3Kšœžœžœ˜C—Kšœ˜K˜—š Ÿœžœ8žœ žœžœ˜hKšœ žœ˜Kšœ*žœ˜0Kšœ3žœžœžœ ˜Nšœ˜šžœ˜šœ žœ˜Kšžœ žœžœ)žœ˜IKšžœ˜ —šžœ˜ Kšžœ žœžœ)žœ˜IKšžœ˜ —K˜—Kšœžœ˜ K˜%Kšœ'˜'K˜-K˜'K˜EK˜Kšœ˜—Kšœ!˜!šžœ ˜Kšœžœžœžœ˜Q—Kšœ˜—K˜Kšœ™K˜šŸ œžœ˜Kšœ žœ˜Kšœ žœ˜K˜K˜!K˜#K˜0Kšœ žœ ˜Kšœ žœ˜Kšœžœ˜ Kšœ˜K˜—š Ÿœžœ žœ žœžœžœ˜^š žœžœžœžœžœž˜;š žœžœžœžœžœž˜;Kšžœžœžœ˜5Kšžœ˜—Kšžœ˜—Kšžœžœ˜ Kšœ˜—K˜Kšœ™K˜˜K˜—š Ÿ œžœžœžœžœžœ˜CKšžœžœ˜.KšœD™DKšœžœ˜Kšœžœ˜K˜K˜š žœ žœžœžœž˜1Kšœ+˜+Kšœ˜Kšžœ˜#Kšžœ˜—Kš 3™3K˜ K˜(K˜šžœžœ˜#šžœžœžœž˜Kšžœžœžœ˜(Kšžœžœžœ˜"K˜Kšžœžœ˜——K˜2Kš ,™,Kšžœ žœ!žœ˜=Kšžœžœžœžœ ˜$˜K˜@—Kš žœžœžœ žœžœ˜)Kšžœ žœžœ žœ˜3K˜K˜šžœžœž˜ Kšœ™Kšžœžœžœžœ ˜"K˜"Kšœ)™)˜K˜L—K˜Kšžœžœžœžœ˜K˜'Kšžœžœ˜.Kšžœ˜—šžœžœžœ˜Kšœ™K˜"šžœ žœ˜Kšœ%™%Kšœ7žœ˜MKšžœ žœžœ'˜;K˜—K˜—Kšœ.˜.K˜Kšœ˜—K˜Kšœ"™"K˜šŸ œžœžœžœ˜QKšœžœžœ˜%Kšœ.™.šžœžœžœžœ˜,K˜Kšœ žœ˜%Kšžœ ˜—šžœžœž˜K˜Kšœ™Kšžœžœžœ ˜+Kšœ#™#šžœ žœžœžœ˜6K˜Kšœ žœ˜%Kšžœ ˜—K˜Kšžœ˜—Kšžœ˜Kšœ˜K˜—šŸ œžœžœ˜KKšœ žœ˜K˜K˜Kšœ˜—K˜Kšœ1™1K˜šŸœžœžœžœ žœ5žœ žœžœ ˜ŸKšœ"™"Kšœ=™=Kšœ žœ˜K˜Kšœ.™.šžœžœžœžœ˜,K˜"Kš žœ žœžœžœžœžœ˜*Kšœ žœ!˜/K˜Kšžœ˜Kšœ˜—šžœžœž˜K˜Kšœ™šžœžœžœ˜Kšœžœžœ˜EKšžœ žœžœ˜Kšœ!™!K˜!Kšžœžœžœ˜7Kšžœ žœžœ˜4Kšžœ˜—K˜Kšœ˜—K˜Kšœ™K˜Kšœ žœžœ˜#šœžœžœ˜Kšœžœ˜Kšœžœ˜šœžœžœžœ˜/Kšœžœ˜Kšœžœ˜K˜——šŸœžœ)žœžœ˜KKšœ&˜&Kšœ˜K˜—š Ÿ œžœžœžœžœ˜š žœžœžœžœžœž˜;K˜&Kšžœ˜—Kšžœ˜—Kšœ™š žœžœžœ žœžœž˜>š žœžœžœžœžœž˜;K˜!Kšžœ˜—Kšžœ˜—Kšœ™š žœžœžœžœžœž˜;š žœžœžœžœžœž˜;Kšžœžœžœžœ˜*K˜1Kšžœžœžœ'˜KKšžœ˜—Kšžœ˜—Kšœ˜K˜—šŸ œžœ˜K˜Kšœžœ˜ š žœžœžœžœžœž˜;š žœžœžœžœžœž˜;Kšœ ˜0š žœžœžœžœžœž˜8Kšœžœ˜Kšžœžœžœ˜š žœžœžœ*žœžœž˜HKšžœžœ žœžœ˜,Kšžœ˜—Kšœ#™#Kšžœžœžœ˜GKšžœ˜—Kšžœ˜—Kšžœ˜—Kšœ˜K˜—šŸ œžœžœžœ ˜,šžœžœžœž˜%Kšžœžœžœžœ˜Kšžœžœžœžœ˜*K˜1Kšžœžœžœžœ˜*K˜!Kšžœ˜—Kšœ˜K˜—šŸ œžœžœžœ˜BKšœA™AKšœžœ˜š žœžœžœ"žœžœž˜@Kšžœžœžœ ˜4Kšžœžœžœ(˜GKšžœžœžœžœ˜&Kšžœžœžœžœ˜$Kšœ˜Kš žœžœžœžœžœ  ˜EKšžœ˜—Kšœžœ˜Kšœ˜K˜—šŸ œžœžœ˜<šžœžœžœžœ˜šžœž˜$Kšžœžœžœ˜#Kšžœžœžœ˜Kšžœ˜—šžœž˜$Kšžœžœžœ˜#Kšžœžœžœ˜Kšžœ˜—Kš žœžœžœžœžœ˜8K˜K˜Kšžœ˜—Kšœ˜K˜—K˜Kšœ-™-Kšœ™Kšœ-™-K˜šŸœžœ žœ˜*Kšœ žœ˜Kšžœ žœžœ˜Kšœ™Kšœ žœD˜Q˜4Kšœ*žœ˜4—K˜šžœžœ˜K˜'K˜.—šžœ žœ˜K˜2K˜'—šžœžœž˜.Kšžœžœžœ˜K˜8K˜-Kšžœ˜—Kšœ˜K™šžœ žœžœ˜Kšœ žœ˜Kšœžœžœ˜šžœ ž˜Kšœ;˜;K˜MKšžœ.˜5—šžœžœ ž˜ Kš žœžœžœžœžœ˜2šžœžœ˜K˜Kšœžœ˜—šžœ žœžœ˜/Kšžœ6˜:Kš žœžœžœžœžœ˜_—Kšœ,˜,Kšžœ˜—Kšœ˜—Kšœ˜K˜—šŸœžœ žœ˜,Kšœ^˜^Kšœc˜cKšœ‰˜‰Kšœ‰˜‰Kšœ}˜}Kšœ˜KšœV˜VKšœU˜UKšœl˜lKšœe˜eKšœk˜kKšœ˜Kšœ˜K˜—šŸœžœ žœ˜.K˜KšœN˜NKšžœžœ˜˜ Kšœ/˜/K˜"—K˜UKšžœžœ˜˜ Kšœ/˜/K˜"—K˜UKšžœžœ˜˜ Kšœ/˜/K˜"—K˜UKšžœžœ˜˜ Kšœ/˜/K˜"—K˜UKšžœ žœžœ ˜9˜ Kšœ/˜/—K˜=˜ K˜R—šžœžœ˜šœ3˜3Kšœ(˜(—KšœLžœ˜mK˜—Kšžœ ž˜šœ0˜0K˜8—Kšžœž˜K˜`K˜XK˜Xšœžœžœ ˜@Kšœ žœžœ ˜1—K˜WKšœ˜Kšœ˜—K˜š Ÿ œžœžœžœžœ˜?šžœžœž˜K˜&K˜(K˜)Kšžœ˜K˜——šŸ œž œ žœ$˜KKšžœžœ žœ žœ˜BK˜)Kšžœ žœ,˜