DIRECTORY AMBridge, AMModel, AMProcess, AMTypes, Basics, BasicTime, CedarProcess, Commander, CommandTool, ComputeServer, ComputeServerControllerRpcControl, ComputeServerInternal, ComputeServerRpcControl, ComputeServerControl, ComputeServerServer, ComputeServerStatistics, FSBackdoor, FSFileOps, Idle, Interpreter, IO, Loader, LupineRuntime, MessageWindow, PrincOps, PrincOpsUtils, PrintTV, Process, ProcessBackdoor, Rope, RPC, SafeStorage, SummonerControllerControl, SymTab, SystemVersion, Terminal, UserCredentials, UserProfile, ViewerClasses, ViewerIO, ViewerOps, VMSideDoor, VMStatistics, WatchStats, WorldVM; ComputeServerMaintenanceImpl: CEDAR MONITOR IMPORTS AMBridge, AMModel, AMProcess, AMTypes, Basics, BasicTime, CedarProcess, Commander, CommandTool, ComputeServerControllerRpcControl, ComputeServerInternal, ComputeServerRpcControl, ComputeServerServer, ComputeServerStatistics, FSBackdoor, FSFileOps, Idle, Interpreter, IO, Loader, LupineRuntime, MessageWindow, PrincOpsUtils, Process, ProcessBackdoor, Rope, RPC, SafeStorage, SummonerControllerControl, SymTab, SystemVersion, UserCredentials, UserProfile, ViewerIO, ViewerOps, VMSideDoor, VMStatistics, WatchStats, WorldVM EXPORTS ComputeServerControl, ComputeServerInternal, ComputeServerStatistics SHARES ComputeServerServer = BEGIN ROPE: TYPE = Rope.ROPE; TimeUpThisIncarnation: PUBLIC INT _ 0; RequestesThisIncarnation: PUBLIC INT _ 0; TimeBusyThisIncarnation: PUBLIC INT _ 0; TotalTimeUp: PUBLIC INT _ 0; TotalRequestes: PUBLIC INT _ 0; TotalTimeBusy: PUBLIC INT _ 0 ; watchStatsRecord: WatchStats.WatchStatsRecord; lastNoGFI: INT _ -1; noGFIChanged: BOOL _ TRUE; LastRMReclamations: INT; LastTime: BasicTime.GMT ; serverMachineName: RPC.ShortROPE; serverMachinePupAddress: RPC.ShortROPE; wasUp: BOOL _ FALSE; isIdle: BOOL _ FALSE; -- is Cedar "idle" or not aConditionSomewhere: LONG POINTER TO CONDITION _ NIL; usingVerticalRetrace: BOOL _ TRUE; verticalRetraceTV: AMTypes.TV; pointerToVerticalRetraceTV: LONG POINTER TO CONDITION _ NIL; myConditionForCPUStats: CONDITION; notifyCPUStatsProcess: LONG POINTER TO PrincOps.ProcessStateBlock _ NIL; PackageEntry: TYPE = ComputeServerInternal.PackageEntry; CmdEntryObject: TYPE = ComputeServerInternal.CmdEntryObject; CmdEntry: TYPE = ComputeServerInternal.CmdEntry; ServiceEnabled: PUBLIC BOOL _ FALSE; IdleTransitionsEnabled: PUBLIC BOOL _ FALSE; whatsActiveProcessDead: CONDITION; whatsActiveProcess: PROCESS _ NIL; active: BOOL _ FALSE; enabled: BOOL _ FALSE; levelCounters: ARRAY PrincOps.Priority OF INT; backgroundSeen: INT _ 0; timeOutType: TYPE = ARRAY [0..maxIndex) OF RECORD[process: PrincOps.PsbIndex, gfh: PrincOps.GlobalFrameHandle, pc: CARDINAL]; timeout: REF timeOutType; maxIndex: CARDINAL = 50; index: CARDINAL _ 0; TerminalImplGF: AMTypes.TV; searchReadyList: BOOLEAN _ TRUE; -- sometimes the user gets the machine gets into a state where searching the ready list is a bad idea. This boolean allows the user to stop the Spy from searching the ready list. maxVerticalRetraceProcessIndex: CARDINAL = 5; verticalRetraceProcesses: ARRAY [0..maxVerticalRetraceProcessIndex) OF RECORD[ psbi: PrincOps.PsbIndex, seenCounter: INT, -- ranges from 0 through 10000; bumped when seen, debumped when not seen seenThisTime: BOOL ]; nextVerticalRetraceProcesses: CARDINAL _ 0; verticalRetraceProcessCount: INT _ 0; maxVerticalRetraceProcessCount: INT = 5; vrpLockSet: BOOL _ FALSE; vrpLockAvailable: CONDITION _ [timeout: 0]; triedToPowerOff: BOOL _ FALSE; ServerOn: Commander.CommandProc = { argv: CommandTool.ArgumentVector _ NIL; controllerName, remoteCommandDirectory, localCommandDirectory: Rope.ROPE _ NIL; IF ServiceEnabled THEN RETURN ["Server already enabled"]; argv _ CommandTool.Parse[cmd ! CommandTool.Failed => { msg _ errorMsg; CONTINUE; }]; IF argv = NIL THEN RETURN[$Failure, msg]; IF argv.argc > 1 THEN controllerName _ argv[1]; IF argv.argc > 2 THEN remoteCommandDirectory _ argv[2]; IF argv.argc > 3 THEN localCommandDirectory _ argv[3]; SummonerControllerControl.StartUpController[controllerName]; msg _ StartUpServer[controllerName, remoteCommandDirectory, localCommandDirectory]; IF ~Rope.IsEmpty[msg] THEN SummonerControllerControl.ShutDownController[]; }; StartUpServer: PUBLIC PROC [controllerName: Rope.ROPE _ NIL, remoteCommandDirectory: Rope.ROPE _ NIL, localCommandDirectory: Rope.ROPE _ NIL] RETURNS [msg: ROPE _ NIL] = { name, password: ROPE; ComputeServerInternal.LastActiveTime _ BasicTime.Now[]; [name, password] _ UserCredentials.Get[]; msg _ ComputeServerInternal.InitCommands[remoteCommandDirectory, localCommandDirectory]; IF ~Rope.IsEmpty[msg] THEN RETURN; ServiceEnabled _ TRUE; ComputeServerInternal.ReportServerEvent[type: serverEnabled, command: NIL, startTime: BasicTime.nullGMT, endTime: BasicTime.nullGMT, remoteMachineName: NIL, userName: NIL]; TRUSTED {Process.Detach[FORK TickStatistics[]]; }; ComputeServerInternal.ControllerGVName _ IF Rope.IsEmpty[controllerName] THEN UserProfile.Token["Summoner.ControllerName"] ELSE controllerName; IF ComputeServerInternal.ControllerGVName = NIL THEN ComputeServerInternal.ControllerGVName _ "PaloAlto1.summoner" ; ComputeServerRpcControl.ExportInterface[ interfaceName: [ type: "ComputeServer.summoner", instance: ComputeServerInternal.MyNetAddressRope, version: [1,1]], user: name, password: RPC.MakeKey[password] ! LupineRuntime.BindingError => CONTINUE;]; ComputeServerInternal.ControllerInterface _ ComputeServerControllerRpcControl.ImportNewInterface[ interfaceName: [ type: "ComputeServerController.summoner", instance: ComputeServerInternal.ControllerGVName, version: [1,1]] ! RPC.ImportFailed => { SummonerControllerControl.ControllerCannnotBeImported[why]; CONTINUE; }; ]; IF ComputeServerInternal.ControllerInterface # NIL THEN { SummonerControllerControl.NoticeControllerIsUp[]; TellControllerAboutExtraCommands[]; }; ComputeServerInternal.InitStats[ComputeServerInternal.myHostName, ComputeServerInternal.MyNetAddressRope]; TRUSTED {Process.Detach[FORK ComputeServerInternal.SummonerServerMonitor[]]; }; StartCounting[]; }; StartCounting: PUBLIC ENTRY PROC = { priority: CedarProcess.Priority; enabled _ TRUE; WHILE whatsActiveProcess # NIL AND ~active DO WAIT whatsActiveProcessDead; ENDLOOP; IF ~active THEN { priority _ CedarProcess.GetPriority[]; CedarProcess.SetPriority[realTime]; TRUSTED {Process.Detach[whatsActiveProcess _ FORK WhatsActive[]]; }; CedarProcess.SetPriority[priority]; }; }; StopCounting: PUBLIC ENTRY PROC = { IF ~active THEN RETURN; -- already stopped enabled _ FALSE; }; WhatsActive: PROC = TRUSTED { topProcess: PrincOps.PsbHandle; lastTopProcess: PrincOps.PsbHandle; noBackgroundProcesses: INT _ 0; done: BOOL _ FALSE; topLevel: Process.Priority _ Process.priorityIdleProcess; sameProcessInARow: INT _ 0; frame: PrincOps.FrameHandle; myPrincOps: PrincOps.PsbHandle = PrincOpsUtils.ReadPSB[]; handleMask: PrincOps.PsbLink = [ failed: FALSE, priority: 0, next: LAST[PrincOps.PsbIndex], reserved: 0, vector: FALSE]; NextHandle: PROC [link: CARDINAL] RETURNS [PrincOps.PsbHandle] = TRUSTED INLINE { RETURN[LOOPHOLE[Basics.BITAND[link, LOOPHOLE[handleMask]]]]}; SearchReadyList: PROC = TRUSTED INLINE { once: BOOLEAN _ FALSE; foundTop: BOOLEAN _ FALSE; headOfReadyList, current: PrincOps.PsbHandle; FOR p: CARDINAL IN [0..nextVerticalRetraceProcesses) DO verticalRetraceProcesses[p].seenThisTime _ FALSE; ENDLOOP; noBackgroundProcesses _ 0; PrincOpsUtils.DisableInterrupts[]; topLevel _ Process.priorityIdleProcess; headOfReadyList _ NextHandle[LOOPHOLE[PrincOps.PDA.ready]]; headOfReadyList _ NextHandle[LOOPHOLE[PrincOps.PDA[headOfReadyList].link]]; -- want the SECOND psb. FOR current _ headOfReadyList, NextHandle[LOOPHOLE[PrincOps.PDA[current].link]] DO psb: LONG POINTER TO PrincOps.ProcessStateBlock = @PrincOps.PDA[current]; link: PrincOps.PsbLink = psb.link; level: Process.Priority = link.priority; skip: BOOLEAN _ FALSE; IF current = headOfReadyList THEN IF once THEN EXIT ELSE once _ TRUE; IF psb = notifyCPUStatsProcess THEN LOOP; IF level = Process.priorityIdleProcess THEN LOOP; IF level = Process.priorityBackground THEN noBackgroundProcesses _ noBackgroundProcesses + 1; IF foundTop THEN LOOP; IF current = myPrincOps THEN LOOP; FOR p: CARDINAL IN [0..nextVerticalRetraceProcesses) DO skipPsb: LONG POINTER TO PrincOps.ProcessStateBlock = @PrincOps.PDA.block[verticalRetraceProcesses[p].psbi]; IF skipPsb = psb THEN { skip _ TRUE; IF level > Process.priorityForeground THEN { IF verticalRetraceProcesses[p].seenCounter < 10000 THEN verticalRetraceProcesses[p].seenCounter _ verticalRetraceProcesses[p].seenCounter + 1; verticalRetraceProcesses[p].seenThisTime _ TRUE; }; EXIT; }; ENDLOOP; IF skip THEN LOOP; IF ~link.vector AND PrincOps.PDA.state[level] = PrincOps.NullStateVectorHandle THEN LOOP; -- no SV. frame _ IF link.vector THEN PrincOps.PDA[psb.context.state].frame ELSE psb.context.frame; FOR i: CARDINAL IN [0..index) DO IF timeout[i].gfh = NIL AND timeout[i].pc = 0 THEN EXIT; IF timeout[i].gfh # frame.accesslink OR timeout[i].pc # frame.pc THEN LOOP; skip _ TRUE; EXIT; ENDLOOP; IF skip THEN LOOP; topLevel _ level; topProcess _ current; foundTop _ TRUE; ENDLOOP; PrincOpsUtils.EnableInterrupts[]; FOR p: CARDINAL IN [0..nextVerticalRetraceProcesses) DO IF verticalRetraceProcesses[p].seenThisTime = FALSE THEN verticalRetraceProcesses[p].seenCounter _ MAX[0, verticalRetraceProcesses[p].seenCounter - 1]; ENDLOOP; }; FOR p: PrincOps.Priority IN PrincOps.Priority DO levelCounters[p] _ 0; ENDLOOP; IF verticalRetraceTV = NIL THEN { [] _ NotifyProcessDead[]; RETURN; }; active _ TRUE; aConditionSomewhere _ pointerToVerticalRetraceTV; usingVerticalRetrace _ TRUE; WHILE ~done DO ticksToRebuildSetTimeouts: INT _ 0; rebuildCounter: INT _ 0; DO waitForBWVerticalRetrace: ENTRY PROC = TRUSTED { WAIT aConditionSomewhere; }; waitForBWVerticalRetrace[]; IF ~enabled THEN EXIT; IF ticksToRebuildSetTimeouts MOD 3 = 0 THEN { IF searchReadyList THEN SearchReadyList[]; levelCounters[topLevel] _ levelCounters[topLevel] + 1; backgroundSeen _ backgroundSeen + noBackgroundProcesses; IF lastTopProcess = topProcess THEN { sameProcessInARow _ sameProcessInARow + 1; IF topLevel > Process.priorityForeground AND sameProcessInARow > 512 THEN { psbi: INT; alreadySkipped: BOOL _ FALSE; psbi _ (topProcess - PrincOps.NullPsbHandle)/SIZE[PrincOps.ProcessStateBlock]; FOR p: CARDINAL IN [0..nextVerticalRetraceProcesses) DO IF verticalRetraceProcesses[p].psbi = psbi THEN { alreadySkipped _ TRUE; EXIT; }; ENDLOOP; IF ~alreadySkipped THEN { Process.Detach[FORK AddToVerticalRetraceProcesses[topProcess]]; }; }; } ELSE { sameProcessInARow _ 0; lastTopProcess _ topProcess; }; }; ticksToRebuildSetTimeouts _ ticksToRebuildSetTimeouts + 1; IF ticksToRebuildSetTimeouts MOD 1024 = 0 THEN { IF index = maxIndex OR (ticksToRebuildSetTimeouts _ ticksToRebuildSetTimeouts + 1) > 7000 THEN { ticksToRebuildSetTimeouts _ 0; rebuildCounter _ rebuildCounter + 1; FOR p: CARDINAL IN [0..nextVerticalRetraceProcesses) DO IF verticalRetraceProcesses[p].seenCounter <= 0 THEN { reArrange: ENTRY PROC = TRUSTED { nextVerticalRetraceProcesses _ nextVerticalRetraceProcesses - 1; IF nextVerticalRetraceProcesses # p THEN { verticalRetraceProcesses[p].psbi _ verticalRetraceProcesses[nextVerticalRetraceProcesses].psbi; verticalRetraceProcesses[p].seenCounter _ verticalRetraceProcesses[nextVerticalRetraceProcesses].seenCounter; }; verticalRetraceProcesses[nextVerticalRetraceProcesses].psbi _ 0; verticalRetraceProcesses[nextVerticalRetraceProcesses].seenCounter _ 0; }; reArrange[]; }; ENDLOOP; IF rebuildCounter > 10 THEN { SetTimeouts[TRUE]; rebuildCounter _ 0; } ELSE SetTimeouts[noGFIChanged]; noGFIChanged _ FALSE; }; }; ENDLOOP; done _ NotifyProcessDead[]; ENDLOOP; Process.SetPriority[Process.priorityNormal]; }; NotifyCPUStats: PROC = { cnt: INT _ 0 ; TRUSTED {notifyCPUStatsProcess _ @PrincOps.PDA.block[(PrincOpsUtils.ReadPSB[] - PrincOps.NullPsbHandle)/SIZE[PrincOps.ProcessStateBlock]]; }; CedarProcess.SetPriority[realTime]; DO doNotify: ENTRY PROC = { NOTIFY myConditionForCPUStats; }; IF usingVerticalRetrace THEN cnt _ cnt + 1 ELSE cnt _ 0; IF cnt > 5 THEN EXIT; doNotify[]; Process.Pause[4]; ENDLOOP; notifyCPUStatsProcess _ NIL; }; AddToVerticalRetraceProcesses: PROC [ topProcess: PrincOps.PsbHandle] = TRUSTED { getLock: ENTRY PROC = TRUSTED { verticalRetraceProcessCount _ verticalRetraceProcessCount + 1; WHILE vrpLockSet DO WAIT vrpLockAvailable ENDLOOP; vrpLockSet _ TRUE; }; freeLock: ENTRY PROC = TRUSTED { verticalRetraceProcessCount _ verticalRetraceProcessCount - 1; vrpLockSet _ FALSE; NOTIFY vrpLockAvailable; }; localWorld: WorldVM.World; rootContext: AMModel.Context; psbi: INT; stack: AMTypes.TV; process: AMProcess.Process; IF verticalRetraceProcessCount > maxVerticalRetraceProcessCount THEN RETURN; CedarProcess.SetPriority[normal]; getLock[]; IF nextVerticalRetraceProcesses >= maxVerticalRetraceProcessIndex THEN { freeLock[]; RETURN; }; localWorld _ WorldVM.LocalWorld[]; rootContext _ AMModel.RootContext[localWorld]; psbi _ (topProcess - PrincOps.NullPsbHandle)/SIZE[PrincOps.ProcessStateBlock]; FOR p: CARDINAL IN [0..nextVerticalRetraceProcesses) DO IF verticalRetraceProcesses[p].psbi = psbi THEN { freeLock[]; RETURN; }; ENDLOOP; process _ AMProcess.PSBIToTV[localWorld, psbi]; AMProcess.Freeze[LIST[process], LIST[rootContext]]; AMProcess.Adjust[LIST[process], LIST[rootContext]]; [stack: stack] _ AMProcess.GetState[process]; { IF stack # NIL THEN { str: IO.STREAM = IO.ROS[]; gf: AMTypes.TV; gf _ AMTypes.GlobalParent[stack]; AMProcess.Thaw[LIST[process]]; IF AMTypes.TVEq[gf, TerminalImplGF ! AMTypes.Error => GOTO bailout] THEN { insert: ENTRY PROC = TRUSTED { IF nextVerticalRetraceProcesses >= maxVerticalRetraceProcessIndex THEN { freeLock[]; RETURN; }; FOR p: CARDINAL IN [0..nextVerticalRetraceProcesses) DO IF verticalRetraceProcesses[p].psbi = psbi THEN { freeLock[]; RETURN; }; ENDLOOP; verticalRetraceProcesses[nextVerticalRetraceProcesses].psbi _ psbi; verticalRetraceProcesses[nextVerticalRetraceProcesses].seenCounter _ 10000; nextVerticalRetraceProcesses _ nextVerticalRetraceProcesses + 1; }; insert[]; }; } ELSE AMProcess.Thaw[LIST[process]]; freeLock[]; EXITS bailout => freeLock[]; }; }; NotifyProcessDead: ENTRY PROC RETURNS[exit: BOOL _ TRUE] = INLINE { IF enabled THEN RETURN [FALSE] ELSE { NOTIFY whatsActiveProcessDead; active _ FALSE; whatsActiveProcess _ NIL; }; }; ServerOff: Commander.CommandProc = { ShutDownServer[]; SummonerControllerControl.ShutDownController[]; }; ShutDownServer: PUBLIC PROC [] RETURNS [] = { ComputeServerInternal.ControllerInterface _ NIL ; ServiceEnabled _ FALSE; ComputeServerInternal.ReportServerEvent[type: serverDisabled, command: NIL, startTime: BasicTime.nullGMT, endTime: BasicTime.nullGMT, remoteMachineName: NIL, userName: NIL]; ComputeServerInternal.PackageTable _ SymTab.Create[mod: 59, case: FALSE]; ComputeServerInternal.ConfigTable _ SymTab.Create[mod: 59, case: FALSE]; StopCounting[]; }; DoStartup: PROC = { msg: ROPE; SummonerControllerControl.StartUpController[ComputeServerInternal.ControllerGVName]; msg _ StartUpServer[ComputeServerInternal.ControllerGVName, ComputeServerInternal.RemoteCommandDir, ComputeServerInternal.LocalCommandDir]; IF ~Rope.IsEmpty[msg] THEN { SummonerControllerControl.ShutDownController[]; MessageWindow.Append[message: Rope.Concat["Can't Start Summoner Server because ", msg], clearFirst: TRUE]; }; }; IdleTransition: Idle.IdleHandler = { SELECT reason FROM becomingBusy => { isIdle _ FALSE; }; becomingIdle => { isIdle _ TRUE; }; ENDCASE; IF IdleTransitionsEnabled THEN { SELECT reason FROM becomingBusy => { ShutDownServer[]; SummonerControllerControl.ShutDownController[]; }; becomingIdle => { TRUSTED {Process.Detach[FORK DoStartup[]];}; }; ENDCASE; }; }; EnableAutoIdle: Commander.CommandProc = { IdleTransitionsEnabled _ TRUE; }; DisableAutoIdle: Commander.CommandProc = { IdleTransitionsEnabled _ FALSE; }; SendStats: PUBLIC PROC [] RETURNS [success, terminateService, newPackage: BOOL _ FALSE, queueingCommands: LIST OF Rope.ROPE] = { controllerInterface: ComputeServerControllerRpcControl.InterfaceRecord; numberCPUs: CARDINAL _ 1; diskPartionSize: INT; freePagesOnDisk: INT; freeboard: INT; oldestLRUFileDate: BasicTime.GMT; freeProcesses: CARDINAL _ 0; totalSamples: INT _ 0; idleBackGroundSamples: INT _ 0; nonBackgroundCPULoad: REAL; aveBackgroundLoad: REAL; NowRMReclamations: INT; ReclamationRate: REAL; NowTime: BasicTime.GMT = BasicTime.Now[]; TimePeriod: REAL; firstCall: BOOL; [diskPartionSize, freePagesOnDisk, freeboard] _ FSBackdoor.VolumePages[]; watchStatsRecord _ WatchStats.GetWatchStats[]; IF watchStatsRecord.gfiFree # lastNoGFI THEN { noGFIChanged _ TRUE; lastNoGFI _ watchStatsRecord.gfiFree; }; NowRMReclamations _ VMStatistics.rmReclamations; TimePeriod _ BasicTime.Period[LastTime, NowTime]; TimePeriod _ MAX[1.0, TimePeriod]; IF LastRMReclamations = 0 THEN ReclamationRate _ -1.0 ELSE ReclamationRate _ (NowRMReclamations - LastRMReclamations) / TimePeriod ; LastRMReclamations _ NowRMReclamations; oldestLRUFileDate _ FSFileOps.OldestLruDate[]; freeProcesses _ ProcessBackdoor.CountFreeProcesses[]; FOR p: PrincOps.Priority IN PrincOps.Priority DO delta: INT = levelCounters[p]; levelCounters[p] _ 0; totalSamples _ totalSamples + delta; IF p <= Process.priorityBackground THEN idleBackGroundSamples _ idleBackGroundSamples + delta; ENDLOOP; SELECT TRUE FROM ~isIdle AND ~usingVerticalRetrace => { IF verticalRetraceTV # NIL THEN TRUSTED { aConditionSomewhere _ pointerToVerticalRetraceTV; usingVerticalRetrace _ TRUE; }; }; totalSamples = 0 AND usingVerticalRetrace AND isIdle => TRUSTED { doBroadcast: ENTRY PROC = TRUSTED INLINE { IF oldCondition # NIL THEN BROADCAST oldCondition^; }; oldCondition: LONG POINTER TO CONDITION; oldCondition _ aConditionSomewhere; aConditionSomewhere _ @myConditionForCPUStats; usingVerticalRetrace _ FALSE; doBroadcast[]; Process.Detach[FORK NotifyCPUStats[]]; }; ENDCASE; IF totalSamples = 0 THEN { nonBackgroundCPULoad _ watchStatsRecord.cpuLoad; aveBackgroundLoad _ -1.0; } ELSE { nonBackgroundCPULoad _ MIN[1.0 - ((1.0*idleBackGroundSamples)/totalSamples), watchStatsRecord.cpuLoad]; aveBackgroundLoad _ (1.0 * backgroundSeen) / totalSamples; }; backgroundSeen _ 0; firstCall _ ~wasUp AND ServiceEnabled; controllerInterface _ ComputeServerInternal.ControllerInterface; IF controllerInterface # NIL THEN [terminateService, newPackage, queueingCommands] _ controllerInterface.NewStats[serverMachineName: serverMachineName, serverMachinePupAddress: serverMachinePupAddress, serverUP: ServiceEnabled, firstCall: firstCall, machineType: SystemVersion.machineType, mainMemory: VMSideDoor.rmPages, numberCPUs: numberCPUs, diskPartionSize: diskPartionSize, freePagesOnDisk: freePagesOnDisk, freeboard: freeboard, freeGFI: watchStatsRecord.gfiFree, freeMDS: watchStatsRecord.mdsFree, freeVM: watchStatsRecord.vmFree, oldestLRUFileDate: oldestLRUFileDate, CPULoad: watchStatsRecord.cpuLoad, nonBackgroundCPULoad: nonBackgroundCPULoad, reclamationRate: ReclamationRate, freeProcesses: freeProcesses, userName: UserCredentials.Get[].name, currentRequests: ComputeServerInternal.CurrentRequests, aveBackgroundLoad: aveBackgroundLoad ]; success _ TRUE; wasUp _ ServiceEnabled; }; SummonerServerMonitor: PUBLIC PROC [] RETURNS [] = { delayToNextStat: INT _ 2; WHILE ServiceEnabled DO success: BOOL _ TRUE; terminateService: BOOL _ FALSE; newPackage: BOOL _ FALSE; queueingCommands: LIST OF ROPE; cycleCount: INT _ 0; tryDifferentController: BOOL _ FALSE; WHILE ~tryDifferentController AND ComputeServerInternal.ControllerInterface # NIL AND success AND NOT terminateService AND ServiceEnabled DO Process.Pause[Process.SecondsToTicks[delayToNextStat]]; [success, terminateService, newPackage, queueingCommands] _ ComputeServerInternal.SendStats[ ! RPC.CallFailed => { SummonerControllerControl.ControllerCallFailed[why]; ComputeServerInternal.ControllerInterface _ NIL ; newPackage _ FALSE; queueingCommands _ NIL; CONTINUE; }; ]; IF newPackage THEN { ComputeServerInternal.getNewPackages[]; }; IF queueingCommands # NIL THEN newQueueingCommands[queueingCommands]; newPackage _ FALSE; delayToNextStat _ 9; IF (cycleCount _ cycleCount + 1) > 10 THEN { cycleCount _ 0; ComputeServerInternal.KillOldUnstartedServices[]; }; IF cycleCount = 1 OR cycleCount = 6 THEN { controllerInterface: ComputeServerControllerRpcControl.InterfaceRecord _ ComputeServerInternal.ControllerInterface; IF controllerInterface # NIL THEN { [tryDifferentController: tryDifferentController] _ controllerInterface.GetSomeInfo[ ! RPC.CallFailed => { SummonerControllerControl.ControllerCallFailed[why]; ComputeServerInternal.ControllerInterface _ NIL ; newPackage _ FALSE; queueingCommands _ NIL; CONTINUE; }; ]; }; }; IF ComputeServerInternal.DisableIFIdle AND ComputeServerInternal.ActiveServices = 0 THEN { now: BasicTime.GMT = BasicTime.Now[]; unpack: BasicTime.Unpacked; minuteTime: INT; IF BasicTime.Period[from: ComputeServerInternal.LastActiveTime, to: now] > 3600 THEN { unpack _ BasicTime.Unpack[now]; minuteTime _ unpack.hour*100+unpack.minute; IF minuteTime > ComputeServerInternal.DisableIFIdleAfter OR minuteTime < ComputeServerInternal.DisableIFIdleBefore THEN { triedToPowerOff _ TRUE; ShutDownServer[]; SummonerControllerControl.ShutDownController[]; }; }; }; ENDLOOP; IF ~tryDifferentController AND ComputeServerInternal.ControllerInterface # NIL AND ~ServiceEnabled THEN [success, terminateService] _ ComputeServerInternal.SendStats[ ! RPC.CallFailed => { SummonerControllerControl.ControllerCallFailed[why]; ComputeServerInternal.ControllerInterface _ NIL ; CONTINUE; }; ]; IF terminateService THEN { ServiceEnabled _ FALSE; ComputeServerInternal.ReportServerEvent[type: serverDisabled, command: NIL, startTime: BasicTime.nullGMT, endTime: BasicTime.nullGMT, remoteMachineName: NIL, userName: NIL]; }; IF ServiceEnabled AND (tryDifferentController OR ComputeServerInternal.ControllerInterface = NIL) THEN ComputeServerInternal.ControllerInterface _ ComputeServerControllerRpcControl.ImportNewInterface[ interfaceName: [ type: "ComputeServerController.summoner", instance: ComputeServerInternal.ControllerGVName, version: [1,1]] ! RPC.ImportFailed => { SummonerControllerControl.ControllerCannnotBeImported[why]; CONTINUE; };]; IF ComputeServerInternal.ControllerInterface # NIL THEN { SummonerControllerControl.NoticeControllerIsUp[]; TellControllerAboutExtraCommands[]; }; ComputeServerInternal.InitStats[ComputeServerInternal.myHostName, ComputeServerInternal.MyNetAddressRope]; ComputeServerInternal.KillOldUnstartedServices[]; IF ComputeServerInternal.ControllerInterface = NIL THEN Process.Pause[Process.SecondsToTicks[7]]; ENDLOOP; }; newQueueingCommands: PROC [queueingCommands: LIST OF ROPE] = { FOR q: LIST OF ROPE _ queueingCommands, q.rest UNTIL q = NIL DO foundInCmdTable: BOOL; cmdVal: REF ANY; cmdEntry: CmdEntry; [found: foundInCmdTable, val: cmdVal] _ SymTab.Fetch[x: ComputeServerInternal.CommandTable, key: q.first]; cmdEntry _ NARROW[cmdVal]; IF cmdEntry # NIL THEN { foundPack: BOOL; valPack: REF ANY; packageEntry: PackageEntry; cmdEntry.doQueueing _ TRUE; [found: foundPack, val: valPack] _ SymTab.Fetch[x: ComputeServerInternal.PackageTable, key: cmdEntry.package]; IF ~foundPack THEN LOOP; packageEntry _ NARROW[valPack, PackageEntry]; IF packageEntry.exclusive AND ComputeServerInternal.ActiveServices > 0 THEN LOOP; IF packageEntry.nowActive < packageEntry.maxCountActive THEN { controllerInterface: ComputeServerControllerRpcControl.InterfaceRecord; controllerInterface _ ComputeServerInternal.ControllerInterface; IF controllerInterface # NIL THEN { controllerInterface.MightAcceptQueuedCommand[serverMachineAddress: ComputeServerInternal.MyNetAddressRope, commandName: q.first ! RPC.CallFailed => { SummonerControllerControl.ControllerCallFailed[why]; ComputeServerInternal.ControllerInterface _ NIL ; CONTINUE; }; ]; cmdEntry.okToQueuePosted _ TRUE; }; }; } ELSE { -- might be an extra command - they aren't in the CommandTable foundRegCmd: BOOL; controllerInterface: ComputeServerControllerRpcControl.InterfaceRecord; [found: foundRegCmd] _ SymTab.Fetch[x: ComputeServerServer.Registry, key: q.first]; IF foundRegCmd AND (controllerInterface _ ComputeServerInternal.ControllerInterface) # NIL THEN { controllerInterface.MightAcceptQueuedCommand[serverMachineAddress: ComputeServerInternal.MyNetAddressRope, commandName: q.first ! RPC.CallFailed => { SummonerControllerControl.ControllerCallFailed[why]; ComputeServerInternal.ControllerInterface _ NIL ; CONTINUE; }; ]; }; }; ENDLOOP; }; TellControllerAboutExtraCommands: PROC = { procData: LIST OF ComputeServerServer.RegisteredProcHandle; EachPairProc: SymTab.EachPairAction = { loopList: LIST OF ComputeServerInternal.RegisteredProcHandle; procData _ NARROW[val]; FOR loopList _ procData, loopList.rest UNTIL loopList = NIL DO procHandle: ComputeServerInternal.RegisteredProcHandle _ loopList.first; foundCmdTab: BOOL; tempControllerInterface: ComputeServerControllerRpcControl.InterfaceRecord _ NIL; [found: foundCmdTab] _ SymTab.Fetch[x: ComputeServerInternal.CommandTable, key: procHandle.service]; IF ~foundCmdTab AND (tempControllerInterface _ ComputeServerInternal.ControllerInterface) # NIL THEN { tempControllerInterface.ExtraCommandAvailable[serverMachineName: ComputeServerInternal.MyNetAddressRope, commandName: procHandle.service, version: procHandle.version ! RPC.CallFailed => { SummonerControllerControl.ControllerCallFailed[why]; ComputeServerInternal.ControllerInterface _ NIL ; CONTINUE; }; ]; }; ENDLOOP; }; [] _ SymTab.Pairs[x: ComputeServerServer.Registry, action: EachPairProc]; }; InitStats: PUBLIC PROC [myName: RPC.ShortROPE, myAddress: RPC.ShortROPE] = { serverMachineName _ myName ; serverMachinePupAddress _ myAddress; LastRMReclamations _ VMStatistics.rmReclamations; LastTime _ BasicTime.Now[]; }; TickStatistics: PROC [] = { lastStatTime: BasicTime.GMT _ BasicTime.Now[]; aSecond: Process.Ticks = Process.SecondsToTicks[1]; WHILE ServiceEnabled DO nowTime: BasicTime.GMT _ BasicTime.Now[]; period: INT; period _ BasicTime.Period[lastStatTime, nowTime]; IF period > 0 THEN { TimeUpThisIncarnation _ TimeUpThisIncarnation + period; TotalTimeUp _ TotalTimeUp + period; IF ComputeServerInternal.ActiveServices > 0 THEN { TimeBusyThisIncarnation _ TimeBusyThisIncarnation + period; TotalTimeBusy _ TotalTimeBusy + period; }; }; lastStatTime _ nowTime; Process.Pause[aSecond]; ENDLOOP; TimeUpThisIncarnation _ 0; RequestesThisIncarnation _ 0; TimeBusyThisIncarnation _ 0; }; ServerStatistics: Commander.CommandProc = { IF ServiceEnabled THEN { percentBusy: REAL; realTimeBusyThisIncarnation: REAL _ TimeBusyThisIncarnation; realTimeUpThisIncarnation: REAL _ TimeUpThisIncarnation; percentBusy _ (100.0 * realTimeBusyThisIncarnation) / MAX[1.0, realTimeUpThisIncarnation]; msg _ IO.PutFR["Seconds up %g, requests %g, time busy %g, percent busy %5.2f%%", IO.int[TimeUpThisIncarnation], IO.int[RequestesThisIncarnation], IO.int[TimeBusyThisIncarnation], IO.real[percentBusy] ] } ELSE msg _ "Server not enabled"; }; StartServerEventTypescript: Commander.CommandProc = { TRUSTED {Process.Detach[FORK ServerEventTypescript[]];}; }; ServerEventTypescript: PROC = { ENABLE IO.Error => GOTO ViewerDestroyed; currentViewer: ViewerClasses.Viewer _ NIL; currentStream: IO.STREAM _ NIL; currentEvent: REF READONLY ComputeServerStatistics.ServerEvent _ NIL; currentViewer _ ViewerOps.CreateViewer[flavor: $Typescript, info: [iconic: FALSE, name: "Compute Server ""Server"" Event Log"]]; currentStream _ ViewerIO.CreateViewerStreams[name: "", viewer: currentViewer].out; currentStream.PutF["Starting log at %g\n", IO.time[BasicTime.Now[]]]; DO currentEvent _ ComputeServerStatistics.NextServerEvent[currentEvent]; SELECT currentEvent.type FROM startService => { currentStream.PutF["Start %g for %g (%g) at %g\n", IO.rope[currentEvent.command], IO.rope[currentEvent.remoteUser], IO.rope[currentEvent.remoteMachineName], IO.time[currentEvent.startTime]]; }; doneService => { currentStream.PutF["End %g for %g at %g duration %g seconds\n", IO.rope[currentEvent.command], IO.rope[currentEvent.remoteMachineName], IO.time[currentEvent.endTime], IO.int[BasicTime.Period[currentEvent.startTime, currentEvent.endTime]]]; }; serverEnabled => { currentStream.PutF[" **** Server enabled at %g\n", IO.time[BasicTime.Now[]]];}; serverDisabled => { currentStream.PutF[" **** Server disabled at %g\n", IO.time[BasicTime.Now[]]];}; ENDCASE; ENDLOOP; EXITS ViewerDestroyed => {}; }; SetTimeouts: PROC [computeVerticalRetrace: BOOL] = TRUSTED { ticks: Process.Ticks; cutoff: Process.Ticks; found: BOOLEAN; frame: PrincOps.FrameHandle; IF index = maxIndex THEN { index _ 0; FOR i: CARDINAL IN [0..maxIndex) DO timeout[i].process _ 0; timeout[i].gfh _ PrincOps.NullGlobalFrame; timeout[i].pc _ 0; ENDLOOP; }; cutoff _ Process.SecondsToTicks[10]; ticks _ PrincOpsUtils.ReadPTC[]; FOR psbi: PrincOps.PsbIndex IN [PrincOps.StartPsb..PrincOps.StartPsb+PrincOps.PDA.count) DO block: PrincOps.ProcessStateBlock = PrincOps.PDA.block[psbi]; IF PrincOps.PDA.block[psbi].mds = 0 THEN LOOP; IF PrincOps.PDA.block[psbi].mds - ticks > cutoff THEN LOOP; -- not significant IF PrincOps.PDA.block[psbi].link.vector THEN frame _ PrincOps.PDA[PrincOps.PDA.block[psbi].context.state].frame ELSE frame _ PrincOps.PDA.block[psbi].context.frame; found _ FALSE; FOR i: CARDINAL IN [0..index) DO IF timeout[i].gfh = frame.accesslink AND timeout[i].pc = frame.pc THEN {found _ TRUE; EXIT}; ENDLOOP; IF found THEN LOOP; timeout[index] _ [psbi, frame.accesslink, frame.pc]; index _ index + 1; IF index = maxIndex THEN EXIT; ENDLOOP; }; InitializeBackgroundCounting: PROC = { newContext: AMModel.Context; Loader.MakeProcedureResident[WhatsActive]; Loader.MakeGlobalFrameResident[InitializeBackgroundCounting]; TRUSTED {Process.InitializeCondition[@whatsActiveProcessDead, 10000]; }; timeout _ NEW[timeOutType]; SafeStorage.PinObject[timeout]; SetTimeouts[TRUE]; TRUSTED {newContext _ AMModel.MostRecentNamedContext["TerminalImpl", AMModel.RootContext[WorldVM.LocalWorld[]]];}; TerminalImplGF _ newContext; verticalRetraceTV _ Interpreter.Evaluate["TerminalImpl.verticalRetrace"].result; IF verticalRetraceTV = NIL THEN TRUSTED { usingVerticalRetrace _ FALSE; aConditionSomewhere _ @myConditionForCPUStats; Process.Detach[FORK NotifyCPUStats[]]; } ELSE TRUSTED { usingVerticalRetrace _ TRUE; pointerToVerticalRetraceTV _ AMBridge.PointerFromTV[verticalRetraceTV]; aConditionSomewhere _ pointerToVerticalRetraceTV; }; }; Commander.Register[key: "///Commands/SummonerServerOn", proc: ServerOn, doc: "SummonerServerOn {controllerName {remoteCommandDirectory {localCommandDirectory}}} Turn on use of the Compute Server Service for cluster controllerName using remoteCommandDirectory on a file server and localCommandDirectory on the workstation"]; Commander.Register[key: "///Commands/SummonerServerOff", proc: ServerOff, doc: "Turn off use of the Compute Server Service"]; Commander.Register[key: "///Commands/SummonerEnableAutoIdle", proc: EnableAutoIdle, doc: "Turn on automatic Summoner server toggle at Idle"]; Commander.Register[key: "///Commands/SummonerDisableAutoIdle", proc: DisableAutoIdle, doc: "Turn off automatic Summoner server toggle at Idle"]; Commander.Register[key: "///Commands/SummonerServerTypescript", proc: StartServerEventTypescript, doc: "Start typescript of server events"]; Commander.Register[key: "///Commands/SummonerServerStatistics", proc: ServerStatistics, doc: "Print statistics"]; [] _ Idle.RegisterIdleHandler[IdleTransition]; InitializeBackgroundCounting[]; END. ComputeServerMaintenanceImpl.mesa The Compute Server Statistics program running on the server that informs the controller of its current state. Last Edited by: Bob Hagmann, August 17, 1986 2:09:36 pm PDT Copyright c 1984 by Xerox Corporation. All rights reserved. Variable Declarations All code invoked by this process should be resident Server Startup and Shutdown skip over a process that appears in the ready queue because it just timed out. (The Spy wakes up with all of the other timeouts. Since it is the highest priority, it will run first. All of the other timeouts will appear on the ready list. Most likely, they will just check some condition and then go back to sleep. This will mask the more interesting processes.) we have a good process! MAIN LOOP Get our hands on the verticalRetrace CONDITION in TerminalImpl. This gets a BROADCAST every vertical retrace, if we are in idle or not. Used to be Terminal.WaitForBWVerticalRetrace, but that does not tick when the machine is idle PrintTV.Print[stack, str]; IF Rope.Run["TerminalImpl.InternalWaitForBWVerticalRetrace", 0, IO.RopeFromROS[str], 0, FALSE] > 40 THEN { recheck under monitor to ensure unique entries and no overflow PROC [data: REF, reason: IdleReason] Issue Statistics May Raise RPC.CallFailed Get values for freeGFI, freeMDS, freeVM, and CPULoad IF FSFileSpaceImpl.lru.first = NIL THEN oldestLRUFileDate _ BasicTime.nullGMT ELSE oldestLRUFileDate _ FSFileSpaceImpl.lru.first.used; Get our hands on the bwRetrace CONDITION in TerminalImpl. This gets a BROADCAST every vertical retrace, if we are not idle. This gets a BROADCAST every vertical retrace, if we are in idle or not. Server Monitor Send final message that we are no longer a server Queueing Maintence New Controller Processing Startup Stats Server Stats Server Event Typescript Initialization IF computeVerticalRetrace THEN { newContext: AMModel.Context; newProcesses: LIST OF AMProcess.Process; newContext _ AMModel.MostRecentNamedContext["TerminalImpl", AMModel.RootContext[WorldVM.LocalWorld[]]]; newProcesses _ AMProcess.GetProcesses[LIST[newContext]]; nextVerticalRetraceProcesses _ 0; DO stack: AMTypes.TV; IF newProcesses = NIL THEN EXIT; [stack: stack] _ AMProcess.GetState[newProcesses.first]; IF stack # NIL THEN { str: IO.STREAM = IO.ROS[]; PrintTV.Print[stack, str]; IF Rope.Run["TerminalImpl.InternalWaitForBWVerticalRetrace", 0, IO.RopeFromROS[str], 0, FALSE] > 40 THEN { verticalRetraceProcesses[nextVerticalRetraceProcesses].psbi _ AMProcess.TVToPSBI[newProcesses.first].psbi; verticalRetraceProcesses[nextVerticalRetraceProcesses].seenCounter _ 0; nextVerticalRetraceProcesses _ nextVerticalRetraceProcesses + 1; }; }; AMProcess.Thaw[LIST[newProcesses.first]]; newProcesses _ newProcesses.rest; ENDLOOP; }; Bob Hagmann May 3, 1985 8:24:08 am PDT changes to: ComputeServerStatsImpl, SendStats Bob Hagmann January 17, 1986 8:33:08 am PST Added check for server already enabled in ServerOn changes to: ServerOn Bob Hagmann May 5, 1986 11:43:38 am PDT add idle/background processing and request list code Κ#– "Cedar" style˜headšœ!™!Ibodyšœm™mL™;Jšœ Οmœ1™<code2šΟk ˜ M˜ M˜M˜ M˜Mšœ˜Mšœ ˜ Mšœ ˜ M˜ M˜ Mšœ˜Mšœ"˜"Mšœ˜M˜M˜M˜Mšœ˜Mšœ ˜ Mšœ ˜ M˜M˜ Mšžœ˜M˜M˜M˜Mšœ ˜ M˜M˜Mšœ˜Mšœ˜M˜Mšžœ˜M˜ M˜M˜M˜Mšœ ˜ Mšœ˜Mšœ ˜ M˜M˜ M˜ M˜ M˜ Mšœ ˜ Mšœ˜M˜——šΟnœžœž˜+MšžœŠžœWžœ‘˜MšžœE˜LMšžœ˜Mšœž˜—™Icode˜Nšœ3™3N˜Nšžœžœžœ˜N˜NšŸœžœžœ˜&NšŸœžœžœ˜)NšŸœžœžœ˜(NšŸ œžœžœ˜NšŸœžœžœ˜NšŸ œžœžœ˜N˜Nšœ.˜.Nšœ žœ˜Nšœžœžœ˜Nšœžœ˜Nšœžœ˜Nšœžœ ˜!Nšœžœ ˜'Nšœžœžœ˜NšœžœžœΟc˜0Nš œžœžœžœžœ˜5Nšœžœžœ˜"Nšœžœ˜Nš œžœžœžœžœ˜Nšžœ žœžœžœ˜2Nšœ žœ˜N˜—šœ žœžœžœ˜ N˜>Nšœ žœ˜Nšžœ˜N˜—Nšœ˜Nšœ˜Nšœžœ˜ Nšœžœ˜Nšœ˜Nšžœ>žœžœ˜LNšœ!˜!N˜ šžœ@žœ˜HNšœ ˜ Nšžœ˜Nšœ˜—Nšœ"˜"Nšœ.˜.Nšœ-žœ˜Nšžœžœžœ#ž˜7šžœ)žœ˜1Nšœ ˜ Nšžœ˜Nšœ˜—Nšžœ˜—Nšœ/˜/Nšœžœ žœ˜3Nšœžœ žœ˜3Nšœ-˜-˜šžœ žœžœ˜Nš œžœžœžœžœ˜Nšœ žœ˜Nšœ!˜!Nšœ™Nšœžœ ˜Nšžœ4žœ žœ˜Jšžœ>žœžœžœ™jšœžœžœžœ˜N™>šžœ@žœ˜HNšœ ˜ Nšžœ˜Nšœ˜—šžœžœžœ#ž˜7šžœ)žœ˜1Nšœ ˜ Nšžœ˜Nšœ˜—Nšžœ˜—NšœC˜CNšœK˜KNšœ@˜@N˜—N˜ N˜—Nšœžœžœ ˜%—Nšœ ˜ ˜Nšœ˜—N˜—Nšœ˜N˜—šŸœžœžœžœžœžœžœ˜CNšžœ žœžœžœ˜šœžœ˜Nšžœ˜Nšœ žœ˜Nšœžœ˜N˜—Nšœ˜N˜—š‘ œ˜%Jšœ˜Jšœ/˜/J˜—š‘œžœžœžœ˜-Mšœ,žœ˜1Mšœžœ˜MšœGžœOžœ žœ˜­MšœBžœ˜IMšœAžœ˜HMšœ˜M˜—šŸ œžœ˜Mšœžœ˜ MšœT˜TJšœ‹˜‹šžœžœ˜Jšœ/˜/Jšœdžœ˜jJ˜—J˜—š‘œ˜$Mšžœžœ™$šžœž˜šœ˜Mšœ žœ˜M˜—šœ˜Mšœ žœ˜M˜—Mšžœ˜—M˜šžœžœ˜ šžœž˜šœ˜Mšœ˜Jšœ/˜/M˜—šœ˜Mšžœžœ˜,M˜—Mšžœ˜—M˜—M˜—š‘œ˜*Mšœžœ˜M˜—š‘œ˜+Mšœžœ˜M˜M˜M˜——™šŸ œžœžœžœ)žœžœžœžœžœ˜€Mšœ™MšœG˜GMšœ žœ˜Mšœžœ˜Mšœžœ˜Mšœ žœ˜Mšœžœ˜!Mšœžœ˜Mšœžœ˜Mšœžœ˜Mšœžœ˜Mšœžœ˜M˜Mšœ˜Mšœ˜Mšœ)˜)Mšœ˜Mšœ žœ˜M˜šœI˜IMšœ4™4—Mšœ/˜/šœ.˜.Mšœžœ˜Mšœ%˜%M˜—Mšœ0˜0Mšœ1˜1Mšœ žœ˜"MšžœžœžœJ˜„Mšœ'˜'Nšœ.˜.Nšžœžœžœ'žœ4™†Nšœ5˜5šžœžœž˜1Nšœžœ˜Nšœ˜Nšœ$˜$Nšžœ!žœ7˜^Nšžœ˜—šžœžœž˜šœžœ˜&Nšœ|™|šžœžœžœžœ˜)Mšœ1˜1Mšœžœ˜M˜—M˜—šœžœžœ žœ˜ANšœG™Gšœ žœžœžœ˜*Nšžœžœžœž œ˜4N˜—Nš œžœžœžœž œ˜(Nšœ#˜#Nšœ.˜.Nšœžœ˜Nšœ˜Mšœžœ˜&M˜—Mšžœ˜—šžœžœ˜Nšœ0˜0Nšœ˜N˜—šœžœ˜NšœžœM˜gNšœ:˜:N˜—Mšœ˜Mšœžœ˜&Mšœ@˜@Idefalutšžœžœžœ΄˜ΥOšœ žœ˜Ošœ˜Mšœ˜——™šŸœžœžœžœ˜4Mšœžœ˜šžœž˜Mšœ žœžœ˜Mšœžœžœ˜Mšœ žœžœ˜Mšœžœžœžœ˜Mšœ žœ˜Mšœžœžœ˜%M˜šžœžœ-žœžœ žœžœžœž˜ŒMšœ7˜7šœ\˜\šœžœ˜Mšœ4˜4Mšœ,žœ˜1Mšœ žœ˜Mšœžœ˜Mšžœ˜ Mšœ˜—Mšœ˜—šžœ žœ˜Mšœ'˜'M˜—Mšžœžœžœ'˜EMšœ žœ˜M˜šžœ$žœ˜,M˜Mšœ1˜1M˜—šžœžœžœ˜*Mšœs˜sšžœžœžœ˜#šœVžœ˜jMšœ4˜4Mšœ,žœ˜1Mšœ žœ˜Mšœžœ˜Mšžœ˜ Mšœ˜—M˜O˜—M˜—šžœ%žœ*žœ˜ZMšœžœ˜%M˜Mšœ žœ˜šžœNžœ˜VM˜Mšœ+˜+šžœ7žœ8žœ˜yMšœžœ˜Mšœ˜Jšœ/˜/M˜—M˜—M˜—Mšžœ˜M™1—Mš žœžœ-žœžœžœ?˜¦šœžœ˜Mšœ4˜4Mšœ,žœ˜1Mšžœ˜ Mšœ˜—Mšœ˜šžœžœ˜Mšœžœ˜MšœGžœOžœ žœ˜­M˜M˜—š žœžœžœ-žœžœb˜ΘMšœ|˜|šœžœ˜Mšœ;˜;Mšžœ˜ Mšœ˜——šžœ-žœžœ˜9Mšœ1˜1M˜#M˜—Mšœj˜jMšœ1˜1Mšžœ-žœžœ*˜aMšžœ˜—M˜——™š Οbœžœžœžœžœ˜>š žœžœžœžœžœžœž˜?Mšœžœ˜Mšœžœžœ˜Mšœ˜Mšœj˜jMšœ žœ ˜šžœ žœžœ˜Mšœ žœ˜Mšœ žœžœ˜Mšœ˜Mšœžœ˜Mšœn˜nJšžœ žœžœ˜Jšœžœ˜-Jšžœžœ*žœžœ˜Qšžœ6žœ˜>JšœG˜GMšœ@˜@šžœžœžœ˜#šœ‚žœ˜•Jšœ4˜4Jšœ,žœ˜1Jšžœ˜ Jšœ˜—Jšœ˜Jšœžœ˜ J˜—Jšœ˜—M˜—šœžœ >˜GMšœ žœ˜MšœG˜GMšœS˜Sšžœ žœEžœžœ˜ašœ‚žœ˜•Jšœ4˜4Jšœ,žœ˜1Jšžœ˜ Jšœ˜—Jšœ˜J˜—M˜M˜—Mšžœ˜—M˜——™šŸ œžœ˜*Nšœ žœžœ*˜;šŸ œ˜'Nšœ žœžœ,˜=Nšœ žœ˜šžœ$žœ žœž˜>JšœH˜HJšœ žœ˜JšœMžœ˜QJšœd˜dšžœžœIžœžœ˜fšœ¨žœ˜»Jšœ4˜4Jšœ,žœ˜1Jšžœ˜ Jšœ˜—Jšœ˜J˜—Jšžœ˜—N˜—NšœJ˜JN˜——™ š ‘ œžœžœ žœžœ˜LMšœ˜Mšœ$˜$Mšœ1˜1Mšœ˜Mšœ˜——™ š‘œžœ˜Mšœžœ˜.Mšœ3˜3šžœž˜Mšœžœ˜)Mšœžœ˜ Mšœ1˜1šžœ žœ˜Mšœ7˜7Mšœ#˜#šžœ*žœ˜2Mšœ;˜;Mšœ'˜'M˜—M˜—Mšœ˜Mšœ˜Mšžœ˜—Mšœ˜Mšœ˜Mšœ˜Mšœ˜—š‘œ˜+šžœžœ˜Mšœ žœ˜Mšœžœ˜žœžœžœ™jNšœj™jNšœG™GNšœ@™@N™—N™—Nšœžœ™)Nšœ!™!Nšžœ™—N™—Nšœ˜N˜—šŸœžœ˜&Nšœ˜Nšœ*˜*Nšœ=˜=NšžœA˜HNšœ žœ˜Nšœ˜Nšœ žœ˜Nšžœk˜rNšœ˜NšœP˜Pšžœžœžœžœ˜)Nšœžœ˜Nšœ.˜.Nšœžœ˜&N˜—šœžœžœ˜Nšœžœ˜NšœG˜GNšœ1˜1N˜—Nšœ˜N˜—MšœΓ˜ΓMšœ}˜}Mšœ˜Mšœ˜MšœŒ˜ŒMšœq˜qMšœ.˜.šœ˜M˜——˜Mšžœ˜—™&Nšœ Οr!™-—™+Nšœ2™2Nšœ £™—™'N™4N™——…—{ΈͺΈ