Files
gh-k-dense-ai-claude-scient…/skills/simpy/scripts/resource_monitor.py
2025-11-30 08:30:10 +08:00

346 lines
11 KiB
Python

#!/usr/bin/env python3
"""
SimPy Resource Monitoring Utilities
This module provides reusable classes and functions for monitoring
SimPy resources during simulation. Includes utilities for tracking
queue lengths, utilization, wait times, and generating reports.
"""
import simpy
from collections import defaultdict
from 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 usage
if __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()