I would like to create a system with servers which need time to set-up before being ready to serve. A server is set up whenever there is a customer arriving to the queue, and the the earlier coming customer will seize the server which is ON earlier, like below.
This process has been successfully simulated thanks to the great answer here: Simulating a system of resource with set-up/switch-on times using Simpy
I would like to add another policy to the system. When a customer leaves the system, he checks if there is any other customer who is waiting to be served. If so, he keeps the server remaining ON, otherwise, he turns off the server immediatelty.
Customer 2 sees that Server 2 is ON before Server 1, and sees that no one else is waiting, so he turns off Server 1.
So, 7),8),9),10) happen at the same time, and the occurence of Event 7) triggers the Interuption of the ealier Event 2) in 10).
Is it possible to manage such kind of Interruption in the Server_Management() class?
"""
Simulation of a dynamic server pool
Server pool starts empty and servers are added as needed, but there is a delay
simulating start up time before the server is available to fulfill a resource request
Programmer: Matt
Wrote original version
Programmer: Michael R. Gibbs
Added server check for dynamicaly adding servers
Fixed return of resorces to pool
"""
import simpy
LAM = 8 #arival rate of jobs
MU = 2 #service rate
ALPHA = 12 #set up rate
NUM_SERVERS = 3
MAX_NUM_JOB = 10000000000
UNTIL = 5
num_current_jobs = 0 #global variable which record the number of jobs present in the system
def generate_interarrival():
return np.random.exponential(1/LAM)
def generate_service():
return np.random.exponential(1/MU)
def switch_on():
return np.random.exponential(1/ALPHA)
class Generate_Job():
def arriving_job(env, servers):
global num_current_jobs, num_server_on, leaving_time_list
for i in range(MAX_NUM_JOB):
job = Job(name="Job%01d" % (i))
yield env.timeout(generate_interarrival())
print('{0:.5f}'.format(env.now), job.name, "arrives")
num_current_jobs +=1
env.process(job.handling(env,servers))
class Server_Management():
def check_servers_arriving(env, servers):
global num_current_jobs
"""
Checks the server pool to see if the pool has any avalable servers
if not then add a server, (there will be a delay before added server becomes available)
Call this without a yield so it does not block if a server is added
"""
print('{0:.5f}'.format(env.now), "checking #OFF servers:",max(0,NUM_SERVERS-num_current_jobs+1))
if num_current_jobs <= NUM_SERVERS:
# will need another server
switch_on_time = switch_on()
print('{0:.5f}'.format(env.now), "adding a server at " + '{0:.5f}'.format(env.now + switch_on_time) + " --")
yield env.timeout(switch_on_time) #switch on time
yield servers.put(1)
print('{0:.5f}'.format(env.now), "added a server--")
def check_servers_leaving(env, servers):
global num_current_jobs
"""
Checks the queue to see if there is any customer is waiting
"""
print('{0:.5f}'.format(env.now), "checking queue length:",max(0,num_current_jobs-NUM_SERVERS))
if num_current_jobs >= NUM_SERVERS: #if there is any waiting customer
yield servers.put(1) #Keep the computer remain ON
print('{0:.5f}'.format(env.now), "computer remains ON--")
class Room: # A room containing servers (resource)
def __init__(self, env):
self.computer = simpy.Container(env, capacity = NUM_SERVERS, init = 0)
class Job(object):
def __init__(self,name):
self.name = name
def handling(self, env, servers):
global num_current_jobs, num_server_on, job_list
# added a check to see if a resource pool needs another server.
env.process(Server_Management.check_servers_arriving(env,servers.computer))
print('{0:.5f}'.format(env.now), self.name, "requesting a server--")
with servers.computer.get(1) as req:
yield req
print('{0:.5f}'.format(env.now), self.name, "occupies a server--")
yield env.timeout(generate_service()) #service time
print('{0:.5f}'.format(env.now), self.name, "leaves")
num_current_jobs -= 1
# added a check to see if a resource pool needs another server.
env.process(Server_Management.check_servers_leaving(env,servers.computer))
np.random.seed(0)
env = simpy.Environment()
servers = Room(env)
env.process(Generate_Job.arriving_job(env, servers))
env.run(until = UNTIL)
got curious and coded it out, I need to go dark for a little while and get some paying work done
"""
Simulation of a dynamic server pool
Server pool starts empty and servers are added as needed, but there is a delay
simulating start up time before the server is available to fulfill a resource request
After a server is started a check is made to see if the server is still needed before
it is added to the resouce pool
Programmer: Matt
Wrote original version
Programmer: Michael R. Gibbs
Added server check for dynamicaly adding servers
servers are returned to resouce pool only if needed (get queue size > 0)
"""
import simpy
import numpy as np
LAM = 8 #arival rate of jobs
MU = 2 #service rate
ALPHA = 12 #set up rate
NUM_SERVERS = 5
MAX_NUM_JOB = 50 #10000000000
UNTIL = 10
server_cnt = 0
job_cnt = 0
start_up_list = []
def generate_interarrival():
return np.random.exponential(1/LAM)
def generate_service():
return np.random.exponential(1/MU)
def switch_on():
return np.random.exponential(1/ALPHA)
def return_server(env,servers):
"""
checks if the server is still needed,
if so add back to the resource pool so waiting request can be filled
else, do not add back to resource pool simulating shutdown
"""
global server_cnt, start_up_list, job_cnt
if len(servers.get_queue) > 0:
# server is still needed
yield servers.put(1)
print('{0:.5f}'.format(env.now), "queuing server --")
if server_cnt > job_cnt:
# have a extra server, try to kill starting up server
# first clean up events that have already happend
i = len(start_up_list)-1
while i >= 0:
e = start_up_list[i]
if e.triggered:
start_up_list.pop(i)
i -=1
# kill last added startup process hoping that is the one with longest time before start up finishes
if len(start_up_list) > 0:
e = start_up_list.pop()
e.interrupt()
print('{0:.5f}'.format(env.now), "killing start up server --------------------------------")
else:
print('{0:.5f}'.format(env.now), "shutting down server --")
server_cnt -= 1
def check_servers(env, servers):
"""
Checks the server pool to see if the pool has any avalable servers
if not then add a server, (there will be a delay before added server becomes available)
after the start up delay, check again to see if the server is still needed
Call this without a yield so it does not block if a server is added
"""
global server_cnt
print('{0:.5f}'.format(env.now), "checking server pool", "requests:",len(servers.get_queue), "idel:", servers.level, "servers:", server_cnt)
if len(servers.get_queue) >= servers.level and server_cnt < NUM_SERVERS:
# will need another server
server_cnt += 1
d = switch_on()
startT = env.now + d
print('{0:.5f}'.format(env.now), "adding a server at " + '{0:.5f}'.format(startT) + " --")
try: # catch interrupts exceptions
# start up
yield env.timeout(d) #switch on time
# check if server is still needed
if len(servers.get_queue) > 0:
# still need it so add
yield servers.put(1)
print('{0:.5f}'.format(env.now), "added a server--")
else:
print('{0:.5f}'.format(env.now), "server not needed, not added--")
server_cnt -=1
except:
server_cnt -= 1
print('{0:.5f}'.format(env.now), "server starting at " + '{0:.5f}'.format(startT) + " has been killed --")
class Generate_Job():
def arriving_job(env, servers):
global num_current_jobs, num_server_on, leaving_time_list
for i in range(MAX_NUM_JOB):
job = Job(name="Job%01d" % (i))
yield env.timeout(generate_interarrival())
print('{0:.5f}'.format(env.now), job.name, "arrives")
env.process(job.handling(env,servers))
class Room: # A room containing servers (resource)
def __init__(self, env):
self.computer = simpy.Container(env, capacity = 10000, init = 0)
class Job(object):
def __init__(self,name):
self.name = name
def handling(self, env, servers):
global start_up_list, job_cnt
# added a check to see if a resource pool needs another server.
job_cnt += 1
start_evt = env.process(check_servers(env,servers.computer))
start_up_list.append(start_evt)
print('{0:.5f}'.format(env.now), self.name, "requesting a server--")
with servers.computer.get(1) as req:
yield req
# if the queue is empty then the req is never filled and the next lines are never called
# need to do this before the rescource requests
#
# yield env.timeout(switch_on()) #switch on time
# yield servers.server.put(1)
print('{0:.5f}'.format(env.now), self.name, "occupies a server--")
yield env.timeout(generate_service()) #service time
print('{0:.5f}'.format(env.now), self.name, "leaves")
# containers do not return a resouce at the end of a "with"
# added a put
#yield servers.computer.put(1)
job_cnt -= 1
yield env.process(return_server(env,servers.computer))
np.random.seed(0)
env = simpy.Environment()
servers = Room(env)
env.process(Generate_Job.arriving_job(env, servers))
env.run(until = UNTIL)