'Simpy: How to monitor resource use

I am trying to understand how to collect data for computing resource use, for example, the average number of customers waiting in line. I looked at the documentation at the following link, but it is just too much for me. I am looking for an example of how it is used and how to compute time-based average line length. I appreciate any guidance.

https://simpy.readthedocs.io/en/latest/topical_guides/monitoring.html#monitoring-your-processes



Solution 1:[1]

Resources do not have utilization logging. You will need to collect that yourself. Monkey patching is a way to wrap resource requests with code to collects the stats without changing how resource requests are called. A more simple way is to just make a logger, and add a log call where you need it. That is how I did it in my example. The down side is you have to remember to add the logging code were you need it.

simple resources have the following properties for collecting stats: capacity, count (number of users with a resource, users (list of users with a resource), queue (list of pending resource requests)

"""
A quick example on how to get average line length 
using a custom logger class

programmer: Michael R. Gibbs
"""

import simpy
import numpy as np
import pandas as pd

class LineLogger():
    """
    logs the size of a resource line
    """

    def __init__(self, env):
        self.env = env

        # the log
        self.samples_df = pd.DataFrame(columns=['time','len'])


    def log(self, time, len):
        """
        log a time and length of the resoure request queue

        time:   time the messure is taken
        len:    length of the queue
        """

        self.samples_df = self.samples_df.append({'time':time,'len':len},ignore_index=True)

    def get_ave_line(self):
        """
        finds the time weighted average of the queue length
        """

        # use the next row to figure out how long the queue was at that length
        self.samples_df['time_span'] = self.samples_df['time'].shift(-1) - self.samples_df['time']

        # drop the last row because it would have a infinate time span
        trimed_samples_df = self.samples_df[0:-1]

        ave = np.average(trimed_samples_df['len'], weights=trimed_samples_df['time_span'])

        return ave

def task(env, res, line_logger):
    """
    A simple task that grabs a resouce for a bit of time
    """

    with res.request() as req:  # Generate a request event

        # requester enters queue for resouce
        line_logger.log(env.now,len(res.queue))
        yield req
        # requester got a resource and leaves requeuest queue
        # if the resource was available when the request was made, then time in queue will be 0
        line_logger.log(env.now,len(res.queue))

        # keep resource to build a queue
        yield env.timeout(3.5)

def gen_tasks(env, res, line_logger):
    """
    generates 5 tasks to seize a resource
    building a queue over time
    """

    for i in range(5):
        env.process(task(env,res,line_logger))

        # put some time between requests
        yield env.timeout(1)

if __name__ == '__main__':

    env = simpy.Environment()
    res = simpy.Resource(env, capacity=1)
    line_logger = LineLogger(env)

    env.process(gen_tasks(env,res,line_logger))
    env.run(100)


    print("finish sim")
    print("average queue length is: ",line_logger.get_ave_line())
    
    print()
    print("log data")
    print(line_logger.samples_df)

    print()
    print("done")

Solution 2:[2]

Use a process running in parallel with your main process to monitor utilization. Here is boilerplate code for a generator function you can use in the monitoring process.

data = []    
def monitor_process(env, resource):
"""
Generator for monitoring process 
that shares the environment with the main process
and collects information.
"""
   while True:
      item = (env.now,
              resource.count,
              len(resource.queue))
      data.append(item)
      yield env.timeout(0.25)

This generator function is set up to poll the resource object 4 times each simulation step and puts the result in an array. You can change the polling frequency. Call this generator like so:

env.process(monitor_process(env, target_resource))

When you call env.run(until=120) (for example) to run your main process, this process will run parallel and log resource statistics.

I have implemented monkey-patching for comparison to this approach. Monkey-patching decorates some of a resource's methods with logging features. The code is more elegant but also more complex. Moreover, with monkey-patching, the resource stats will be logged each time an event occurs, i.e. any of the target resource's get, put, request or release methods is called. The approach I have shown here will log resource stats at regular time intervals and the code is relatively simpler.

Hope this helps.

Cheers!

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Michael
Solution 2 Sun Bee