<> <> <> <> <> DIRECTORY Environment USING [PageNumber, wordsPerPage], FastBreak USING [FastBreakProc], Frame USING [GetReturnFrame], Inline USING [BITAND, BITSHIFT, LongDiv], IntervalTimer USING [WaitForExpirationInterval], IntervalTimerFace USING [exists], Mopcodes, PageMap USING [GetF, flagsVacant], PrincOps USING [BytePC, ControlLink, Frame, FrameHandle, GFTIndex, GlobalFrameHandle], PrincOpsRuntime USING [GFT, GetFrame], Process USING [ Detach, GetCurrent, GetPriority, InitializeCondition, Priority, priorityInterrupt, priorityNormal, SecondsToTicks, SetPriority], ProcessInternal USING [DisableInterrupts, EnableInterrupts], ProcessOperations USING [ IndexToHandle, LongNotify, LongReEnter, LongWait, ReadPSB, ReadPTC], PSB USING [ Condition, FaultIndex, NullStateVectorHandle, PsbHandle, PsbIndex, PsbLink, PDA, PDABase, Priority, ProcessStateBlock, PsbNull, QueueEmpty, QueueHandle, qPageFault, StartPsb, Ticks], Rope USING [ROPE], RTProcess USING [GetTotalPageFaults, StartWatchingFaults, StopWatchingFaults], RTStorageAccounting USING [AllocatorCallbackProcForSpy], SafeStorage USING [NWordsAllocated, NWordsReclaimed], SDDefs USING [SD, sGFTLength], SpecialSpace USING [ MakeCodeResident, MakeGlobalFrameResident], SpyClient USING [], SpyLog USING [Here, Initialize, logging, spy, Open, WriteData], SpyOps USING [ Count, DataType, Frame, LevelData, -- SetSensitiveBreak, SpyState, Stack, stackHeader, stackLength, StackType], System USING [GetClockPulses, Pulses, PulsesToMicroseconds], UserTerminal USING [WaitForScanLine], Utilities USING [PageFromLongPointer]; SpyKernelImpl: MONITOR IMPORTS PilotFrame: Frame, IntervalTimer, IntervalTimerFace, Inline, PageMap, PrincOpsRuntime, Process, ProcessInternal, ProcessOperations, RTStorageAccounting, RTProcess, SafeStorage, SpecialSpace, SpyLog, System, UserTerminal, Utilities EXPORTS SpyClient, SpyOps SHARES PageMap = BEGIN OPEN PrincOps, PSB, Rope; -- SpyKernel parameters -- spyState: PUBLIC SpyOps.SpyState _ off; watching: PUBLIC SpyOps.DataType _ CPU; justMe: PUBLIC PSB.PsbIndex _ 0; <> runningTime: PUBLIC System.Pulses; -- total time running pageFaults: PUBLIC LONG CARDINAL; active, starts, stops: PUBLIC INTEGER _ 0; wordsAllocated: PUBLIC LONG CARDINAL; wordsReclaimed: PUBLIC LONG CARDINAL; wakeups: PUBLIC SpyOps.Count _ 0; -- Number of times that Spy counted something notScheduled: PUBLIC SpyOps.Count _ 0; -- no interesting process sceduled skips: PUBLIC SpyOps.Count _ 0; -- counts skipped code: PUBLIC SpyOps.Count _ 0; -- page faults on code data: PUBLIC SpyOps.Count _ 0; -- page faults on data levelData: PUBLIC SpyOps.LevelData _ ALL[0]; <<****************************************************************************>> <> <<****************************************************************************>> InitializeSpy: PUBLIC ENTRY PROCEDURE[ dataType: SpyOps.DataType, process: PSB.PsbIndex] RETURNS[errorMsg: ROPE] = BEGIN SELECT dataType FROM spaces => RETURN["Use UserBreaks on SpaceImplA.CreateAny and SpaceImplA.DeleteAny."]; ENDCASE; DisableSpy[]; InitializeTables[]; IF ~SpyLog.spy THEN ZeroLog[]; watching _ dataType; justMe _ IF dataType = process THEN process ELSE 0; EnableSpy[]; END; SensitiveBreak: PROCEDURE[words: CARDINAL] = BEGIN IF active = 0 THEN RETURN; IF watching NOT IN [allocations..wordsAllocated] THEN RETURN; Record[ psbi: LOOPHOLE[Process.GetCurrent[]], frame: PilotFrame.GetReturnFrame[], count: IF watching = wordsAllocated THEN words ELSE 1]; END; InitializeTables: PROCEDURE = BEGIN runningTime _ [0]; levelData _ ALL[0]; wakeups _ notScheduled _ skips _ code _ data _ 0; wordsAllocated _ wordsReclaimed _ 0; END; Initialize: PROCEDURE = BEGIN dolphin _ System.PulsesToMicroseconds[[1]]#32; -- hack SpecialSpace.MakeCodeResident[SpyKernelImpl]; SpecialSpace.MakeGlobalFrameResident[SpyKernelImpl]; Process.InitializeCondition[@processDead, 10000]; SetTimeouts[]; END; SetTimeouts: PROC = BEGIN ticks: PSB.Ticks; cutoff: PSB.Ticks; found: BOOLEAN; frame: PrincOps.FrameHandle; IF IntervalTimerFace.exists THEN RETURN; cutoff _ Process.SecondsToTicks[10]; ProcessInternal.DisableInterrupts[]; -- stops the clock. NO PAGE FAULTS ALLOWED! ticks _ ProcessOperations.ReadPTC[]; FOR psbi: PsbIndex IN [PSB.StartPsb..PSB.StartPsb+PSB.PDA.count) DO IF PSB.PDA.block[psbi].mds = 0 THEN LOOP; IF PSB.PDA.block[psbi].mds - ticks > cutoff THEN LOOP; -- not significant IF PSB.PDA.block[psbi].link.vector THEN frame _ PDA[PSB.PDA.block[psbi].context.state].frame ELSE frame _ PSB.PDA.block[psbi].context.frame; found _ FALSE; FOR i: CARDINAL IN [0..index) DO IF timeout[i] = [frame.accesslink.gfi, frame.pc] THEN {found _ TRUE; EXIT}; ENDLOOP; IF found THEN LOOP; timeout[index] _ [frame.accesslink.gfi, frame.pc]; index _ index + 1; IF index = maxIndex THEN EXIT; ENDLOOP; ProcessInternal.EnableInterrupts[]; END; ZeroLog: PUBLIC PROCEDURE = { SpyLog.Initialize[NIL, 40, TRUE]; SpyLog.Open[TRUE]}; <<****************************************************************************>> <> <<****************************************************************************>> <> processDead: CONDITION; spyProcess: PROCESS _ NIL; oldSpyState: SpyOps.SpyState _ off; startTime: System.Pulses; startPageFaults: LONG CARDINAL; startWordsAllocated: LONG CARDINAL; startWordsReclaimed: LONG CARDINAL; StartCounting: PUBLIC ENTRY FastBreak.FastBreakProc = BEGIN i: CARDINAL; priority: Process.Priority; starts _ starts + 1; IF active = 0 THEN WHILE spyProcess # NIL DO WAIT processDead; ENDLOOP; IF (active _ active + 1) > 1 THEN RETURN[useOldBreak: FALSE]; -- a psuedo start IF watching = pagefaults THEN FOR i IN [0..10) DO RTProcess.StopWatchingFaults[] ENDLOOP ELSE RTProcess.StartWatchingFaults[]; startPageFaults _ RTProcess.GetTotalPageFaults[]; startWordsAllocated _ SafeStorage.NWordsAllocated[]; startWordsReclaimed _ SafeStorage.NWordsReclaimed[]; startTime _ System.GetClockPulses[]; IF watching = breakProcess THEN justMe _ LOOPHOLE[Process.GetCurrent[]]; IF justMe # 0 THEN justMePSB _ @PSB.PDA[ProcessOperations.IndexToHandle[justMe]]; SetTimeouts[]; -- do it a second time in case the first time missed some priority _ Process.GetPriority[]; Process.SetPriority[Process.priorityInterrupt]; SELECT watching FROM CPU, process, breakProcess => Process.Detach[spyProcess _ FORK Spy[]]; pagefaults => Process.Detach[spyProcess _ FORK PageFaultRecorder[]]; allocations, wordsAllocated => { RTStorageAccounting.AllocatorCallbackProcForSpy _ SensitiveBreak; spyState _ on}; ENDCASE => spyState _ on; Process.SetPriority[priority]; RETURN[useOldBreak: FALSE]; END; StopCounting: PUBLIC ENTRY FastBreak.FastBreakProc = BEGIN IF active = 0 THEN RETURN[useOldBreak: FALSE]; -- already stopped stops _ stops + 1; IF (active _ active - 1) > 0 THEN RETURN; -- a psuedo stop spyState _ off; RTStorageAccounting.AllocatorCallbackProcForSpy _ NIL; runningTime _ [runningTime + System.GetClockPulses[] - startTime]; pageFaults _ pageFaults + RTProcess.GetTotalPageFaults[] - startPageFaults; wordsAllocated _ wordsAllocated + SafeStorage.NWordsAllocated[] - startWordsAllocated; wordsReclaimed _ wordsReclaimed + SafeStorage.NWordsReclaimed[] - startWordsReclaimed; IF watching = pagefaults THEN RTProcess.StartWatchingFaults[] ELSE RTProcess.StopWatchingFaults[]; RETURN[useOldBreak: FALSE]; END; DisableSpy: PROCEDURE = BEGIN IF spyState = disabled THEN RETURN; oldSpyState _ spyState; spyState _ disabled; END; EnableSpy: PROCEDURE = {spyState _ oldSpyState}; <<*********************************************************************>> <> <<*********************************************************************>> <> justMePSB: LONG POINTER TO ProcessStateBlock _ NIL; wait: CARDINAL _ 0; -- used with dolphins monitor: BOOLEAN _ FALSE; -- measure performance of spy dolphin: BOOLEAN _ FALSE; maxStackDepth: CARDINAL = 200; timeout: ARRAY [0..maxIndex) OF RECORD[gfi: PrincOps.GFTIndex, pc: CARDINAL]; maxIndex: CARDINAL = 20; index: CARDINAL_ 0; sampleInterval: INT _ 10*1000; -- measured in microseconds Spy: PROCEDURE = BEGIN top: PsbIndex; frame: FrameHandle; myPSB: PsbHandle = ProcessOperations.ReadPSB[]; handleMask: PsbLink = [failed: FALSE, priority: 0, next: LAST[PsbIndex], reserved: 0, vector: FALSE]; NextHandle: PROCEDURE [link: UNSPECIFIED] RETURNS [PsbHandle] = INLINE {RETURN[Inline.BITAND[link, handleMask]]}; HandleToIndex: PROCEDURE [psb: PsbHandle] RETURNS [PsbIndex] = INLINE {RETURN[Inline.BITSHIFT[psb, -2]]}; SearchReadyList: PROCEDURE = INLINE BEGIN skip, once: BOOLEAN _ FALSE; headOfReadyList, current: PsbHandle; ProcessInternal.DisableInterrupts[]; top _ PSB.PsbNull; headOfReadyList _ NextHandle[PDA.ready]; headOfReadyList _ NextHandle[PDA[headOfReadyList].link]; -- want the SECOND psb. FOR current _ headOfReadyList, NextHandle[PDA[current].link] DO psb: LONG POINTER TO ProcessStateBlock = @PDA[current]; link: PsbLink = psb.link; level: Process.Priority = link.priority; IF current = headOfReadyList THEN IF once THEN EXIT ELSE once _ TRUE; IF level = 0 THEN LOOP; IF current = myPSB THEN LOOP; IF ~link.vector AND PDA.state[level] = NullStateVectorHandle THEN LOOP; -- no SV. frame _ IF link.vector THEN PDA[psb.context.state].frame ELSE psb.context.frame; <> <<(The Spy wakes up with all of the other timeouts. Since it is the highest priority,>> <> <> <> skip _ FALSE; FOR i: CARDINAL IN [0..maxIndex) DO IF timeout[i] = [0, 0] THEN EXIT; IF timeout[i] # [frame.accesslink.gfi, frame.pc] THEN LOOP; skip _ TRUE; EXIT; ENDLOOP; IF skip THEN LOOP; <> top _ HandleToIndex[current]; EXIT; ENDLOOP; ProcessInternal.EnableInterrupts[]; END; spyState _ on; -- MAIN LOOP -- DO IF IntervalTimerFace.exists THEN IntervalTimer.WaitForExpirationInterval[sampleInterval] ELSE UserTerminal.WaitForScanLine[0]; IF active <= 0 THEN EXIT; IF spyState = disabled THEN LOOP; IF dolphin AND wait > 0 THEN {wait _ wait - 1; LOOP} ELSE wait _ 8; IF monitor THEN SpyLog.Here[]; SearchReadyList[]; SELECT TRUE FROM justMe = 0 => Record[top, frame]; justMePSB.link.failed => Record[justMe, NIL, 2]; -- waiting ML justMePSB.flags.waiting => Record[justMe, NIL, 3]; -- waiting CV OnQueue[justMe, @PDA.fault[PSB.qPageFault].queue] => Record[justMe, NIL, 1]; -- waiting pagefault OnQueue[justMe, @PDA.fault[PSB.qPageFault+4].queue] => Record[justMe, NIL, 1]; -- waiting pagefault OnQueue[justMe, @PSB.PDA.ready] => SELECT TRUE FROM PDA.state[justMePSB.link.priority] = NullStateVectorHandle AND ~justMePSB.link.vector => Record[justMe, NIL, 5]; -- waiting SV justMe # top => Record[justMe, NIL, 4]; -- prempted by a higher priority process ENDCASE => Record[justMe]; -- ready ENDCASE => Record[justMe, NIL, 6]; -- in some unknown state IF monitor THEN SpyLog.Here[]; ENDLOOP; spyProcess _ NIL; spyState _ off; NotifyProcessDead[]; Process.SetPriority[Process.priorityNormal]; END; OnQueue: PROC[psbi: PsbIndex, queueHandle: PSB.QueueHandle] RETURNS[BOOL] = INLINE BEGIN tail, prev: PsbIndex; IF queueHandle^ = PSB.QueueEmpty THEN RETURN[FALSE]; prev _ tail _ queueHandle.tail; THROUGH [FIRST[PsbIndex]..LAST[PsbIndex]+1] -- garbage protection -- DO next: PsbIndex = PSB.PDA.block[prev].link.next; IF next = psbi THEN RETURN[TRUE]; prev _ next; IF prev = tail THEN RETURN[FALSE]; ENDLOOP; RETURN[FALSE] -- actually, the queue is thoroughly mangled! -- END; <<*********************************************************************>> <> <<*********************************************************************>> recordPageFaulted: BOOLEAN _ FALSE; Data: TYPE = RECORD[process: CARDINAL, page: INTEGER]; CodeBase: TYPE = LONG POINTER TO PACKED ARRAY [0..0) OF Mopcodes.op; PageFaultRecorder: PROCEDURE = -- stolen from Ben.mesa BEGIN fault: Data; type: CARDINAL; codePage: INTEGER; handle: PSB.PsbHandle; frame: PrincOps.FrameHandle; pda: PSB.PDABase = PSB.PDA; qPageFault: PSB.FaultIndex = PSB.qPageFault; pPageFaultCondition: LONG POINTER TO PSB.Condition = @pda.fault[qPageFault].condition; pPageFaultCONDITION: LONG POINTER TO CONDITION = LOOPHOLE[pPageFaultCondition]; recorderLock: MONITORLOCK; shouldNotifyPilot: BOOLEAN _ FALSE; <> spyState _ on; DO <> ProcessOperations.LongWait[@recorderLock, pPageFaultCONDITION, 1]; UNTIL ProcessOperations.LongReEnter[@recorderLock, pPageFaultCONDITION] DO NULL ENDLOOP; IF pda.fault[qPageFault].queue.tail = PSB.PsbNull THEN { -- timed out IF shouldNotifyPilot AND pda.fault[qPageFault].condition.tail ~= PSB.PsbNull THEN { LongNakedNotify[pPageFaultCONDITION]; shouldNotifyPilot _ FALSE}; IF active <= 0 THEN EXIT; LOOP}; -- go back and wait again. <
> fault.process _ pda.block[pda.fault[qPageFault].queue.tail].link.next; -- walk to tail, then to head. handle _ ProcessOperations.IndexToHandle[fault.process]; fault.page _ Utilities.PageFromLongPointer[pda[pda[handle].context.state].memPointer]; frame _ IF pda[handle].link.vector THEN pda[pda[handle].context.state].frame ELSE pda[handle].context.frame; codePage _ LOOPHOLE[frame.accesslink.code.longbase+frame.pc/2, INT]/256; SELECT TRUE FROM ABS[codePage - fault.page] <= 1 => {code _ code + 1; type _ 2}; Xfer[frame.accesslink.code.longbase, frame.pc] => {code _ code + 1; type _ 3}; ENDCASE => {data _ data + 1; type _ 1}; <> ProcessInternal.DisableInterrupts[]; IF pPageFaultCondition^.tail = PSB.PsbNull THEN shouldNotifyPilot _ TRUE -- Pilot not ready for this fault yet... ELSE LongNakedNotify[pPageFaultCONDITION]; ProcessInternal.EnableInterrupts[]; <> IF active <= 0 THEN EXIT; IF recordPageFaulted THEN SpyLog.WriteData[CODE[Data], SIZE[Data], @fault]; Record[fault.process, NIL, type]; ENDLOOP; spyProcess _ NIL; spyState _ off; NotifyProcessDead[]; Process.SetPriority[Process.priorityNormal]; END; Xfer: PROCEDURE[base: CodeBase, pc: PrincOps.BytePC] RETURNS[BOOLEAN] = INLINE BEGIN -- is the current pc pointing to some sort of xfer? IF base[pc] IN [Mopcodes.zEFC0..Mopcodes.zKFCB] THEN RETURN[TRUE]; RETURN[FALSE]; END; LongNakedNotify: PROCEDURE [pCondition: LONG POINTER TO CONDITION] = INLINE { <> pCond: LONG POINTER TO PSB.Condition = LOOPHOLE[pCondition]; ProcessInternal.DisableInterrupts[]; IF pCond^.tail=PSB.PsbNull THEN {pCond^.wakeup _ TRUE; ProcessInternal.EnableInterrupts[]} ELSE {ProcessInternal.EnableInterrupts[]; ProcessOperations.LongNotify[pCondition]}}; NotifyProcessDead: ENTRY PROCEDURE = {NOTIFY processDead}; <<***************************************************************************>> <> <<***************************************************************************>> stack: SpyOps.Stack; UserBreak: PUBLIC FastBreak.FastBreakProc = TRUSTED BEGIN count: CARDINAL _ 1; Parameters: TYPE = RECORD[ zone: ZONE, size: CARDINAL, filler: ARRAY [0..11) OF CARDINAL]; IF spyState # on THEN RETURN[useOldBreak: FALSE]; IF watching # userDefined THEN RETURN[useOldBreak: FALSE]; IF watching = wordsAllocated THEN count _ LOOPHOLE[sv.stk, Parameters].size; Record[LOOPHOLE[Process.GetCurrent[]], frame, count]; RETURN[useOldBreak: FALSE]; END; Record: PUBLIC ENTRY PROCEDURE[psbi: PsbIndex, frame: FrameHandle _ NIL, type: SpyOps.StackType _ 0, count: CARDINAL _ 1] = BEGIN level: Process.Priority; psb: LONG POINTER TO ProcessStateBlock; IF spyState = disabled THEN RETURN; IF active <= 0 THEN RETURN; wakeups _ wakeups + count; IF psbi = PSB.PsbNull THEN IF PDA.fault[qPageFault].queue.tail = PSB.PsbNull AND PDA.fault[qPageFault+4].queue.tail = PSB.PsbNull THEN {notScheduled _ notScheduled + count; RETURN} ELSE {skips _ skips + count; RETURN}; -- waiting for a page fault psb _ @PDA[ProcessOperations.IndexToHandle[psbi]]; level _ psb.link.priority; IF frame = NIL THEN frame _ IF psb.link.vector THEN PDA[psb.context.state].frame ELSE psb.context.frame; levelData[level] _ levelData[level] + count; LogStack[psbi, level, frame, count, type] END; LogStack: INTERNAL PROC [process: PSB.PsbIndex, level: Process.Priority, frame: FrameHandle, count, type: CARDINAL] = -- Log the stack on the Trace Log -- INLINE BEGIN sdLength: CARDINAL = SDDefs.SD[SDDefs.sGFTLength]; gfi: GFTIndex _ 0; gf: GlobalFrameHandle _ NIL; length: CARDINAL _ 0; GfiToGF: PROCEDURE [gfi: GFTIndex] RETURNS [GlobalFrameHandle] = INLINE {RETURN[PrincOpsRuntime.GetFrame[PrincOpsRuntime.GFT[gfi]]]}; ValidFrame: PROCEDURE [f: ControlLink] RETURNS [FrameHandle] = BEGIN OPEN Environment, PageMap; <> <> IF f.proc OR f.indirect OR f.frame =NIL OR (gf _ f.frame.accesslink) = NIL OR GetF[Inline.LongDiv[LOOPHOLE[LONG[gf]], wordsPerPage]].flags = flagsVacant OR (gfi _ gf.gfi) > sdLength OR GfiToGF[gfi] ~= gf THEN RETURN[NIL]; RETURN[f.frame] END; IF ~SpyLog.logging THEN RETURN; stack.type _ type; stack.count _ count; stack.level _ level; stack.process _ process; frame _ ValidFrame[LOOPHOLE[frame]]; THROUGH [0..maxStackDepth) UNTIL frame = NIL DO <> <> IF length >= SpyOps.stackLength THEN {stack.level _ 7; length _ 0; EXIT}; stack.frame[length] _ [0, gfi, frame.pc]; IF length > 0 AND stack.frame[length] = stack.frame[length-1] THEN length _ length - 1; -- skip recursive frames length _ length + 1; frame _ ValidFrame[frame.returnlink]; ENDLOOP; IF length = 0 AND stack.level < 7 THEN {notScheduled _ notScheduled + count; RETURN} ELSE SpyLog.WriteData[SpyOps.Stack.CODE, SpyOps.stackHeader + length*SpyOps.Frame.SIZE, @stack]; END; Initialize[]; <<22-Jan-82 Maxwell: Removed cross-partition code; converted to Cedar>> <<4-Feb-82 Maxwell: Added stack logging option>> <<11-Mar-82 Maxwell: Added page fault recorder>> <> <<>> END. <<>> <> <> <<[gfi, pc] pairs indicate WAITs on timeouts.>> timeout[0] _ [gfi: 100B, pc: 114B]; <> timeout[1] _ [gfi: 100B, pc: 143B]; <> timeout[2] _ [gfi: 401B, pc: 2316B]; <> timeout[3] _ [gfi: 401B, pc: 3307B]; <> timeout[4] _ [gfi: 1075B, pc: 12265B]; <> timeout[5] _ timeout[6] _ timeout[7] _ timeout[8] _ timeout[9] _ [0, 0]; END;