在监控资源使用情况时请求资源。

huangapple go评论62阅读模式
英文:

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)}')

huangapple
  • 本文由 发表于 2023年3月23日 08:59:31
  • 转载请务必保留本文链接:https://go.coder-hub.com/75818447.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定