345 lines · 11.4 KB · Python
RawDownload
#!/usr/bin/env python3"""SimPy Resource Monitoring Utilities This module provides reusable classes and functions for monitoringSimPy resources during simulation. Includes utilities for trackingqueue lengths, utilization, wait times, and generating reports.""" import simpyfrom collections import defaultdictfrom typing import List, Tuple, Dict, Any  class ResourceMonitor:    """    Monitor resource usage with detailed statistics tracking.     Tracks:    - Queue lengths over time    - Resource utilization    - Wait times for requests    - Request and release events    """     def __init__(self, env: simpy.Environment, resource: simpy.Resource, name: str = "Resource"):        """        Initialize the resource monitor.         Args:            env: SimPy environment            resource: Resource to monitor            name: Name for the resource (for reporting)        """        self.env = env        self.resource = resource        self.name = name         # Data storage        self.queue_data: List[Tuple[float, int]] = [(0, 0)]        self.utilization_data: List[Tuple[float, float]] = [(0, 0.0)]        self.request_times: Dict[Any, float] = {}        self.wait_times: List[float] = []        self.events: List[Tuple[float, str, Dict]] = []         # Patch the resource        self._patch_resource()     def _patch_resource(self):        """Patch resource methods to intercept requests and releases."""        original_request = self.resource.request        original_release = self.resource.release         def monitored_request(*args, **kwargs):            req = original_request(*args, **kwargs)             # Record request event            queue_length = len(self.resource.queue)            utilization = self.resource.count / self.resource.capacity             self.queue_data.append((self.env.now, queue_length))            self.utilization_data.append((self.env.now, utilization))            self.events.append((self.env.now, 'request', {                'queue_length': queue_length,                'utilization': utilization            }))             # Store request time for wait time calculation            self.request_times[req] = self.env.now             # Add callback to record when request is granted            def on_granted(event):                if req in self.request_times:                    wait_time = self.env.now - self.request_times[req]                    self.wait_times.append(wait_time)                    del self.request_times[req]             req.callbacks.append(on_granted)            return req         def monitored_release(*args, **kwargs):            result = original_release(*args, **kwargs)             # Record release event            queue_length = len(self.resource.queue)            utilization = self.resource.count / self.resource.capacity             self.queue_data.append((self.env.now, queue_length))            self.utilization_data.append((self.env.now, utilization))            self.events.append((self.env.now, 'release', {                'queue_length': queue_length,                'utilization': utilization            }))             return result         self.resource.request = monitored_request        self.resource.release = monitored_release     def average_queue_length(self) -> float:        """Calculate time-weighted average queue length."""        if len(self.queue_data) < 2:            return 0.0         total_time = 0.0        weighted_sum = 0.0         for i in range(len(self.queue_data) - 1):            time1, length1 = self.queue_data[i]            time2, length2 = self.queue_data[i + 1]            duration = time2 - time1            total_time += duration            weighted_sum += length1 * duration         return weighted_sum / total_time if total_time > 0 else 0.0     def average_utilization(self) -> float:        """Calculate time-weighted average utilization."""        if len(self.utilization_data) < 2:            return 0.0         total_time = 0.0        weighted_sum = 0.0         for i in range(len(self.utilization_data) - 1):            time1, util1 = self.utilization_data[i]            time2, util2 = self.utilization_data[i + 1]            duration = time2 - time1            total_time += duration            weighted_sum += util1 * duration         return weighted_sum / total_time if total_time > 0 else 0.0     def average_wait_time(self) -> float:        """Calculate average wait time for requests."""        return sum(self.wait_times) / len(self.wait_times) if self.wait_times else 0.0     def max_queue_length(self) -> int:        """Get maximum queue length observed."""        return max(length for _, length in self.queue_data) if self.queue_data else 0     def report(self):        """Print detailed statistics report."""        print(f"\n{'=' * 60}")        print(f"RESOURCE MONITOR REPORT: {self.name}")        print(f"{'=' * 60}")        print(f"Simulation time: 0.00 to {self.env.now:.2f}")        print(f"Capacity: {self.resource.capacity}")        print(f"\nUtilization:")        print(f"  Average: {self.average_utilization():.2%}")        print(f"  Final: {self.resource.count / self.resource.capacity:.2%}")        print(f"\nQueue Statistics:")        print(f"  Average length: {self.average_queue_length():.2f}")        print(f"  Max length: {self.max_queue_length()}")        print(f"  Final length: {len(self.resource.queue)}")        print(f"\nWait Time Statistics:")        print(f"  Total requests: {len(self.wait_times)}")        if self.wait_times:            print(f"  Average wait: {self.average_wait_time():.2f}")            print(f"  Max wait: {max(self.wait_times):.2f}")            print(f"  Min wait: {min(self.wait_times):.2f}")        print(f"\nEvent Summary:")        print(f"  Total events: {len(self.events)}")        request_count = sum(1 for _, event_type, _ in self.events if event_type == 'request')        release_count = sum(1 for _, event_type, _ in self.events if event_type == 'release')        print(f"  Requests: {request_count}")        print(f"  Releases: {release_count}")        print(f"{'=' * 60}")     def export_csv(self, filename: str):        """        Export monitoring data to CSV file.         Args:            filename: Output CSV filename        """        import csv         with open(filename, 'w', newline='') as f:            writer = csv.writer(f)            writer.writerow(['Time', 'Event', 'Queue Length', 'Utilization'])             for time, event_type, data in self.events:                writer.writerow([                    time,                    event_type,                    data['queue_length'],                    data['utilization']                ])         print(f"Data exported to {filename}")  class MultiResourceMonitor:    """Monitor multiple resources simultaneously."""     def __init__(self, env: simpy.Environment):        """        Initialize multi-resource monitor.         Args:            env: SimPy environment        """        self.env = env        self.monitors: Dict[str, ResourceMonitor] = {}     def add_resource(self, resource: simpy.Resource, name: str):        """        Add a resource to monitor.         Args:            resource: SimPy resource to monitor            name: Name for the resource        """        monitor = ResourceMonitor(self.env, resource, name)        self.monitors[name] = monitor        return monitor     def report_all(self):        """Generate reports for all monitored resources."""        for name, monitor in self.monitors.items():            monitor.report()     def summary(self):        """Print summary statistics for all resources."""        print(f"\n{'=' * 60}")        print("MULTI-RESOURCE SUMMARY")        print(f"{'=' * 60}")        print(f"{'Resource':<20} {'Avg Util':<12} {'Avg Queue':<12} {'Avg Wait':<12}")        print(f"{'-' * 20} {'-' * 12} {'-' * 12} {'-' * 12}")         for name, monitor in self.monitors.items():            print(f"{name:<20} {monitor.average_utilization():<12.2%} "                  f"{monitor.average_queue_length():<12.2f} "                  f"{monitor.average_wait_time():<12.2f}")         print(f"{'=' * 60}")  class ContainerMonitor:    """Monitor Container resources (for tracking level changes)."""     def __init__(self, env: simpy.Environment, container: simpy.Container, name: str = "Container"):        """        Initialize container monitor.         Args:            env: SimPy environment            container: Container to monitor            name: Name for the container        """        self.env = env        self.container = container        self.name = name        self.level_data: List[Tuple[float, float]] = [(0, container.level)]         self._patch_container()     def _patch_container(self):        """Patch container methods to track level changes."""        original_put = self.container.put        original_get = self.container.get         def monitored_put(amount):            result = original_put(amount)             def on_put(event):                self.level_data.append((self.env.now, self.container.level))             result.callbacks.append(on_put)            return result         def monitored_get(amount):            result = original_get(amount)             def on_get(event):                self.level_data.append((self.env.now, self.container.level))             result.callbacks.append(on_get)            return result         self.container.put = monitored_put        self.container.get = monitored_get     def average_level(self) -> float:        """Calculate time-weighted average level."""        if len(self.level_data) < 2:            return self.level_data[0][1] if self.level_data else 0.0         total_time = 0.0        weighted_sum = 0.0         for i in range(len(self.level_data) - 1):            time1, level1 = self.level_data[i]            time2, level2 = self.level_data[i + 1]            duration = time2 - time1            total_time += duration            weighted_sum += level1 * duration         return weighted_sum / total_time if total_time > 0 else 0.0     def report(self):        """Print container statistics."""        print(f"\n{'=' * 60}")        print(f"CONTAINER MONITOR REPORT: {self.name}")        print(f"{'=' * 60}")        print(f"Capacity: {self.container.capacity}")        print(f"Current level: {self.container.level:.2f}")        print(f"Average level: {self.average_level():.2f}")        print(f"Utilization: {self.average_level() / self.container.capacity:.2%}")         if self.level_data:            levels = [level for _, level in self.level_data]            print(f"Max level: {max(levels):.2f}")            print(f"Min level: {min(levels):.2f}")         print(f"{'=' * 60}")  # Example usageif __name__ == "__main__":    def example_process(env, name, resource, duration):        """Example process using a resource."""        with resource.request() as req:            yield req            print(f"{name} started at {env.now}")            yield env.timeout(duration)            print(f"{name} finished at {env.now}")     # Create environment and resource    env = simpy.Environment()    resource = simpy.Resource(env, capacity=2)     # Create monitor    monitor = ResourceMonitor(env, resource, "Example Resource")     # Start processes    for i in range(5):        env.process(example_process(env, f"Process {i}", resource, 3 + i))     # Run simulation    env.run()     # Generate report    monitor.report()