英文:
Requesting resources when monitoring the resource usage
问题
我尝试监控我的资源,并根据文档中的解释这里做到了。
我已经成功实现了,您可以在下面的代码中看到。我的问题是当我有多个资源实例时,我无法弄清楚如何调用一个可用的资源,或者在请求时它们都忙碌时如何等待直到有一个可用。
以下是一个简单的模型,其中一个项目依次经过两个任务,两个任务都由我有十个实例的相同资源完成。我将资源实例添加到一个字典中,同时还显示了记录的时间是在第一个任务还是第二个任务中完成的。
# 代码部分
正如您在这段代码中所看到的,我使用了项目ID来调用十个资源中的一个,使用id%len(10)作为调用特定资源的技巧,使代码工作。我想要能够调用这十个资源中的任何一个来执行一个任务。如果所有十个都不可用,那么进程会等待直到有一个可用。
我尝试使用循环迭代资源并查找可用的资源,如下所示:
# 代码部分
不幸的是,这没有返回任何结果,所有记录的时间都是零。我希望有人能帮助解决这个问题。另外,我想按ID记录每个项目的时间。问题是,我在哪些地方记录这些时间,使用的是env.now(),是否正确?正如您所看到的,我有task1_queue、task1_start等等。我只想确保我在正确的地方记录了这些时间。
谢谢大家。这个社区在我学习SimPy的过程中非常有帮助。
英文:
I try to monitor my resources and as explained in the documentation here
I was able to achieve that as you can see in the code below. My issue is when I have multiple instance of a resource, I cannot figure out how to call an available resource, or wait until one becomes available if they all are busy when requested.
Here is a simple model where an item goes through two tasks sequentially and both tasks are done by the same resource that I have ten instances of. I add the resource instance to a dictionary along with a label showing whether the recorded time was done in the first or second task
import simpy
import random
def arrival(env, worker, arrival_time, task1_mean, task1_std, task2_mean, task2_std):
id = 0
while True:
w = first_task(env, worker, task1_mean, task1_std, task2_mean, task2_std, id)
env.process(w)
yield env.timeout(random.expovariate(1/arrival_time))
id += 1
def first_task(env, worker, task1_mean, task1_std, task2_mean, task2_std, id):
task1_queue = env.now
with worker[id % len(10)]['time'].request() as req:
task1_start = env.now
worker[id % len(10)]['task'].append('Task 1')
yield req
yield env.timeout(abs(random.normalvariate(task1_mean, task1_std)))
task1_end = env.now
w = first_task(env, worker, task2_mean, task2_std, id)
env.process(w)
def second_task(env, worker, task2_mean, task2_std, id):
task2_queue = env.now
with worker[id % len(10)]['time'].request() as req:
worker[id % len(10)]['task'].append('Task 2')
task2_start = env.now
yield req
yield env.timeout(abs(random.normalvariate(task2_mean, task2_std)))
task2_end = env.now
class TimeMonitoredResource(simpy.Resource):
def __init__(self,*args,**kwargs):
super().__init__(*args,**kwargs)
self.start_time = None
self.end_time = None
self.working_time = []
self.starting_time = []
self.releasing_time = []
def request(self,*args,**kwargs):
self.start_time = self._env.now
return super().request(*args,**kwargs)
def release(self,*args,**kwargs):
self.end_time = self._env.now
self.working_time.append(self.end_time - self.start_time)
self.starting_time.append(self.start_time)
self.releasing_time.append(self.end_time)
return super().release(*args,**kwargs)
def calculate_metrics(op):
total_operation_time = []
total_starting_time = []
total_releasing_time = []
total_operation_time = op.working_time
total_starting_time = op.starting_time
total_releasing_time = op.releasing_time
return total_starting_time, total_releasing_time, total_operation_time
env = simpy.Environment()
worker = {}
for i in range(10):
worker[i] = {'time': TimeMonitoredResource(env, 1), 'task': []}
task1_mean, task1_std, task2_mean, task2_std = 6, 2, 8, 2
arrival_time = 2
env.process(arrival(env, worker, arrival_time, task1_mean, task1_std, task2_mean, task2_std))
env.run(1200)
worker_op_start = {}
worker_op_release = {}
worker_op_total = {}
worker_op_task = {}
for i, op in worker.items():
start, release, total = calculate_metrics(op['time'])
worker_op_start[i] = start
worker_op_release[i] = release
worker_op_release[i] = total
worker_op_task[i] = op['task']
As you can see in this code I used the item id to call one of the ten resources using id % len(10) as a trick to call a specific resource and make the code work. What I want to be able to achieve is calling any of the ten resources to do a task. If all ten are not available, then the process wait until one becomes available.
I tried to use a loop and iterate over the resources and find an available one as follows:
for _, resource in worker.items():
if resource['time'].count == 0:
with resource['time'].request() as req:
resource['task'].append('Task 1')
yield env.timeout(abs(random.normalvariate(task1_mean, task1_std)))
yield req
break
Unfortunately, this retuned nothing and all the recorded times were zeros. I hope someone can help with this. Also I want to record the time for each item by id. The question is, is the places where I recorcd those times using env.now() correct or not? As you can see I have task1_queue, task1_start, etc. I just want to make sure I'm recording these times in the right place.
Thank you all. This community has been really helpful in my SimPy journey.
答案1
得分: 2
问题在于使用 simpy.Resource 收集资源级别的资源指标时,simpy.Resource 没有资源对象。它只有可用/部署资源的计数。我创建了一个资源对象来收集指标,并使用了一个 simpy.Store 来管理请求。我将 simpyStore 封装在一个类中,以在请求和释放时更新资源对象。使用 simpy store 的问题是它没有上下文管理器来自动将资源返回到 simpy.Store,就像 simpy.Resouce 池一样。所以我创建了一个特殊的 simpy Event,通过添加一个上下文管理器来包装 simpy.Store 的 get(),并在 "with" 退出时添加了一个释放调用。没有进行太多测试,但在正常情况下可以工作。所以在这个示例中,你得到了两件事情。一个带有上下文管理器的 simpy.Store,会在 "with" 退出时返回资源,并且会收集资源级别的使用指标。比我想象的要复杂一些。
英文:
So the problem with collecting resource metrics at the resource level with a simpy.Resource is simpy.Resource does not have resouce objects. It only has a count of available / deployed resources. I created a resource object to collect the metrics and used a simpy.Store to manage the requests. I wrapped the simpyStore in a class that updates the resource objects as they are requested and released. The issue with a simpy store is that it does not have a context manager to automatically return the resources to the simpy.Store like a simpy.Resouce pool does. So I created a special simpy Event by adding a context manager which wraps the simpy.Store get(), and adds a release call on the "with" exit. Did not do a lot of testing, but it works in the happy path. So your getting two things in this example. a simpy.Store with a context manager the returns resources on exit of the "with", and resources that collect usage metrics at the resource level. This was a bit more trick then I thought it would be.
"""
Demo of resources where each resource collects usage data
Composes a resource pool that uses a simpy.Store to hold
resouces objects. The pool updates the resouce stats as
they are requested and released
Creatd a special event to act as a context manager for
resource requests
programmer: Michael R. Gibbs
"""
import simpy
import random
class ResourceEvent(simpy.Event):
"""
A event for rquesting a resouce.
Is also a context manager so
'with' syntax can be use to
atomatic release the resource
event returns a resouce when it succeeds
"""
def __init__(self, env, request_process, release_process):
"""
starts the process to get a resouce
saves processes
parameters
----------
request_process: process
a process to get the resource
release_process: process
a process to release a resouce
"""
super().__init__(env)
self.env = env
self.request_process = request_process
self.release_process = release_process
env.process(self._echo())
def _echo(self):
"""
process to get a resouce
triggers this event when process gets the resouce
returning the resource
"""
self.request_event = self.env.process(self.request_process())
resource = yield self.request_event
# save resouce so it can be released later
self.resource = resource
self.succeed(resource)
def __enter__(self):
"""
Context enter, returns self
"""
return self
def __exit__(self, *arg, **karg):
"""
Context exit
if the resource request was successful, then
return the resouce
else
cancel the pending resouce request
"""
if self.request_event.triggered:
self.release_process(self.resource)
else:
self.request_event.cancel()
class MyResourcePool():
"""
Wraps a simpy.Store to behaive more like a simpy.Resouce
Uses resouces objects that collect usage stats
"""
def __init__(self, env, recourse_cnt):
"""
creates a simpy.Store and load it with the desirec
number of resouces
parameter
---------
env: simpy.Environment
resourse_cnt: int
number of resouces in the pool
"""
self.env = env
self._pool = simpy.Store(env)
self.resources = [] # master list to simplify resouce interation
for i in range(recourse_cnt):
resource = self.MyResource(i+1)
self.resources.append(resource)
self._pool.put(resource)
def _request_event(self):
"""
helper method to get a resouce
and to update metrics for the resouce
"""
print(f'{self.env.now} requesting a resouce')
request_start = self.env.now
# using a "with" insures the get gets
# cancled if this process gets canceled
with self._pool.get() as req:
resource = yield req
request_time = self.env.now - request_start
resource.req_wait_time += request_time
idle = self.env.now - resource._relase_time
resource.idle_time += idle
# save dispatch time to calc the busy/work time later
resource._dispatch_time = self.env.now
resource.busy = True
print(f'{self.env.now} resouce {resource.id} has been deployed')
return resource
def request(self):
"""
Creates a event that returns a resouce when a resouce
becomes available
event is also a context manager so it can be uses
in a 'with' syntax
"""
req = ResourceEvent(self.env, self._request_event, self.release)
return req
def release(self, resource):
"""
Release a resouce and updates it stats
"""
print(f'{self.env.now} resouce {resource.id} has been released')
if resource in self.resources:
work = self.env.now - resource._dispatch_time
resource.busy_time += work
resource._relase_time = self.env.now
resource.busy = False
self._pool.put(resource)
else:
raise Exception("Trying to release a object that does not belong to pool")
class MyResource():
"""
Resouce class for collecting usage stats
"""
def __init__(self, id):
"""
initalize collectors and trackers
"""
self.id = id
self.busy = False
self.req_wait_time = 0
self.busy_time = 0
self.idle_time = 0
self._dispatch_time = 0
self._relase_time = 0
def use_resouce(env, pool):
"""
test grabing a resource
"""
with myPool.request() as req:
resource = yield req
yield env.timeout(random.triangular(1,3,10))
def test(env, myPool):
# doing a test without the with syntax
req = myPool.request()
resource = yield req
yield env.timeout(5)
myPool.release(resource)
# do real stress test
for _ in range(20):
env.process(use_resouce(env, myPool))
yield env.timeout(random.triangular(1,1,4))
env = simpy.Environment()
myPool = MyResourcePool(env, 3)
env.process(test(env, myPool))
env.run(100)
print()
print('done')
for r in myPool.resources:
print(f'id: {r.id}, busy: {r.busy_time}, idle: {r.idle_time}, usage: {r.busy_time / (r.busy_time + r.idle_time)}')
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论