Scaling Up without Breaking the Bank › Monitoring Usage and Catching Bottlenecks
AI Agent HandbookAll chapters Scaling Up without Breaking the Bank › Monitoring Usage and Catching BottlenecksOne section · Sections in this chapter5 / 13 ↓
Scaling isn't just about adding capacity. It's about knowing when and where to add it. This requires monitoring.
What should you monitor? Three key metrics:
Response time: How long does it take to handle a request? If this starts increasing, you're approaching capacity limits.
Request rate: How many requests per minute are you handling? This tells you if usage is growing and helps you plan capacity.
Error rate: How many requests fail? A spike in errors often indicates you've exceeded capacity or hit rate limits.
Here's a simple monitoring setup:
## Using Claude Sonnet 4.5 for agent operations
import time
from collections import deque
from datetime import datetime, timedelta
class AgentMonitor:
def __init__(self, window_minutes=5):
self.window = timedelta(minutes=window_minutes)
self.requests = deque()
self.errors = deque()
self.response_times = deque()
def record_request(self, success=True, response_time=None):
"""Record a request and its outcome"""
now = datetime.now()
self.requests.append(now)
if not success:
self.errors.append(now)
if response_time:
self.response_times.append((now, response_time))
# Clean old data outside the window
self._clean_old_data(now)
def _clean_old_data(self, now):
"""Remove data older than the monitoring window"""
cutoff = now - self.window
while self.requests and self.requests[0] < cutoff:
self.requests.popleft()
while self.errors and self.errors[0] < cutoff:
self.errors.popleft()
while self.response_times and self.response_times[0][0] < cutoff:
self.response_times.popleft()
def get_stats(self):
"""Get current statistics"""
if not self.requests:
return {
"requests_per_minute": 0,
"error_rate": 0,
"avg_response_time": 0,
}
window_minutes = self.window.total_seconds() / 60
requests_per_minute = len(self.requests) / window_minutes
error_rate = (
len(self.errors) / len(self.requests) if self.requests else 0
)
if self.response_times:
avg_time = sum(t for _, t in self.response_times) / len(
self.response_times
)
else:
avg_time = 0
return {
"requests_per_minute": requests_per_minute,
"error_rate": error_rate,
"avg_response_time": avg_time,
}
## Use the monitor
monitor = AgentMonitor(window_minutes=5)
## Process requests and record metrics
for query in queries:
start = time.time()
try:
result = process_request(query)
elapsed = time.time() - start
monitor.record_request(success=True, response_time=elapsed)
except Exception as e:
elapsed = time.time() - start
monitor.record_request(success=False, response_time=elapsed)
print(f"Error: {e}")
## Check stats
stats = monitor.get_stats()
print(f"Requests/min: {stats['requests_per_minute']:.1f}")
print(f"Error rate: {stats['error_rate']:.1%}")
print(f"Avg response time: {stats['avg_response_time']:.2f}s")This monitor tracks requests over a rolling time window. You can check statistics at any time to see how your agent is performing.
When should you scale up? Watch for these warning signs:
Response times increase: If your average response time starts climbing from 2 seconds to 4 seconds to 6 seconds, you're running out of capacity. Time to add more workers or instances.
Error rates spike: If errors jump from 1% to 5% or 10%, you're likely hitting rate limits or overwhelming your servers.
Request queues grow: If you're using a queue to buffer requests, watch its length. A growing queue means requests are arriving faster than you can process them.
In production systems, you'd typically send these metrics to a monitoring service like Datadog, Prometheus, or CloudWatch. These tools can alert you when metrics cross thresholds, so you can respond before users notice problems.