Cost Management › Cost Monitoring

Michael BrenndoerferFebruary 14, 202611 min read
Language AI HandbookAll chapters
Part 65 of 67Production Systems
Chapter 499Cost Management › Cost Monitoring
Reading section 5 of 8 · Cost Monitoring · 11 minSection 5 of 8 · 11 min

Book contents

Section 5 of 8 · 11 min read

Cost Management › Cost MonitoringOne section · Sections in this chapter5 / 8 ↓
Reading tools Definitions & inline links

Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.

Article links

Make inline references clickable

Cost monitoring creates continuous visibility into spending, enabling early detection of anomalies, enforcement of budgets, and tracking of optimization progress. Without monitoring, cost problems are often discovered only at month-end when the bill arrives.

Key Cost Metrics

An effective cost monitoring system tracks metrics at multiple granularities:

Token-level metrics (collected per request):

  • Prompt token count and completion token count
  • Request cost in dollars (computed from token counts and current rates)
  • Cost efficiency ratio: output quality score divided by cost (requires quality evaluation)

Request-level metrics (aggregated over time windows):

  • Requests per minute and tokens per minute, broken down by model tier
  • Cost per request and cost per token, with trend tracking
  • Cache hit rates for prefix caching and semantic caching
  • Token rejection rates from per-user budget enforcement

Infrastructure metrics (from the hosting platform):

  • GPU utilization (compute and memory), both instantaneous and averaged
  • Throughput (tokens/sec) vs. theoretical maximum
  • Cost per GPU-hour and effective cost per token
  • Reserved instance utilization and on-demand overflow costs

Business metrics (connecting cost to value):

  • Cost per active user per day
  • Cost per successfully completed task (e.g., per support ticket resolved, per code review)
  • Cost as a percentage of revenue or allocated budget
In[16]:
Code
# Cost monitoring pipeline with anomaly detection
from collections import deque

import numpy as np


class CostMonitor:
    """
    Rolling cost monitor with anomaly detection using
    exponential weighted moving average (EWMA).
    """

    def __init__(self, window_minutes: int = 60, alert_multiplier: float = 2.5):
        self.window_minutes = window_minutes
        self.alert_multiplier = alert_multiplier
        # Store (timestamp, cost) tuples
        self.cost_history = deque()
        self.ewma_cost = None
        self.ewma_alpha = 0.1  # Smoothing factor

    def record(self, timestamp: float, cost: float):
        """Record a cost observation and update EWMA."""
        self.cost_history.append((timestamp, cost))

        # Remove observations outside the window
        cutoff = timestamp - self.window_minutes * 60
        while self.cost_history and self.cost_history[0][0] < cutoff:
            self.cost_history.popleft()

        # Update EWMA
        if self.ewma_cost is None:
            self.ewma_cost = cost
        else:
            self.ewma_cost = (
                self.ewma_alpha * cost + (1 - self.ewma_alpha) * self.ewma_cost
            )

    def is_anomaly(self, cost: float) -> bool:
        """Compare a new observation with the EWMA of prior observations."""
        if self.ewma_cost is None or self.ewma_cost == 0:
            return False
        return cost > self.alert_multiplier * self.ewma_cost

    def window_total(self) -> float:
        """Total cost in the current window."""
        return sum(cost for _, cost in self.cost_history)

    def window_rate(self) -> float:
        """Estimated cost rate in USD per hour."""
        if not self.cost_history:
            return 0.0
        span_seconds = self.cost_history[-1][0] - self.cost_history[0][0]
        if span_seconds < 1:
            return 0.0
        return self.window_total() / span_seconds * 3600


# Simulate monitoring with an injected cost spike
monitor = CostMonitor(window_minutes=60, alert_multiplier=2.5)
alerts = []
timestamps = []
costs = []
ewma_values = []
alert_thresholds = []

rng = np.random.default_rng(seed=42)
base_time = time.time() - 7200  # Start 2 hours ago

for minute in range(120):
    t = base_time + minute * 60
    # Normal traffic with some noise
    normal_cost = 0.05 + rng.normal(0, 0.01)
    # Inject a cost spike at minute 75 (runaway batch job scenario)
    if 75 <= minute <= 85:
        request_cost = normal_cost * 8
    else:
        request_cost = max(0.001, normal_cost)

    threshold_before_update = (
        monitor.alert_multiplier * monitor.ewma_cost
        if monitor.ewma_cost is not None
        else np.nan
    )
    is_alert = monitor.is_anomaly(request_cost)
    if is_alert:
        alerts.append(minute)

    monitor.record(t, request_cost)

    timestamps.append(minute)
    costs.append(request_cost)
    ewma_values.append(monitor.ewma_cost)
    alert_thresholds.append(threshold_before_update)
Out[17]:
Console
Cost Monitoring Summary (120-minute window):

  Current window total: $6.73
  Estimated rate: $6.73/hr
  Current EWMA: $0.0555/min
  Anomaly alerts fired at minutes: [75, 76, 77, 78]
  Total alerts: 4
Out[18]:
Visualization
Time series of simulated cost per minute, its EWMA baseline, and a threshold based on the prior EWMA. A shaded spike spans minutes 75 to 85, and markers show the early spike observations that fire alerts.
Simulated per-minute cost over 120 minutes with an injected spike between minutes 75 and 85. Each observation is compared with a threshold equal to 2.5 times the prior EWMA before the baseline is updated. Alert markers identify the minutes that exceed that threshold.

Budget Enforcement

Cost monitoring without enforcement is incomplete. Budget enforcement closes the loop by taking action when spending approaches or exceeds thresholds.

A layered enforcement approach works well in practice:

Soft limits trigger alerts and notifications without blocking requests. When spending reaches 70% of a daily budget, an automated notification goes to the responsible team. At 90%, escalation occurs to engineering leadership. This approach preserves service availability while creating urgency for investigation.

Hard limits block new requests when a budget is exhausted. This is appropriate for non-critical, optional features but must be applied carefully to avoid breaking user-facing functionality. Hard limits are more commonly applied to per-user quotas than to organizational budgets.

Rate limiting caps the rate of spending rather than total spend. Rather than blocking all requests after a budget is hit, rate limiting ensures the system spends at most a certain amount per hour. This is more graceful for users, as it slows down expensive operations rather than blocking them entirely.

Graceful degradation automatically routes requests to cheaper model tiers when spending is high. A user who would normally receive a response from a 70B model might receive one from a 7B model when the budget is constrained. This requires that alternative tiers are available and that quality degradation is acceptable for the use case.

In[19]:
Code
# Budget enforcement with graceful degradation
class BudgetEnforcer:
    """
    Enforces spending budgets with graceful degradation.
    Routes to cheaper tiers as budget utilization increases.
    """

    def __init__(self, daily_budget_usd: float):
        self.daily_budget_usd = daily_budget_usd
        self.spent_today = 0.0
        # Tier selection thresholds
        self.tier_policy = [
            # (max_utilization, allowed_tier, reason)
            (0.70, "large", "Normal operation"),
            (0.85, "medium", "Approaching budget, degrading to medium"),
            (0.95, "small", "Near budget limit, degrading to small"),
            (1.00, None, "Budget exhausted, requests blocked"),
        ]

    def get_allowed_tier(self, requested_tier: str) -> tuple:
        """
        Return the tier to use given current budget utilization.
        Returns (actual_tier, reason) where actual_tier may be None (block).
        """
        utilization = self.spent_today / self.daily_budget_usd
        for max_util, allowed_tier, reason in self.tier_policy:
            if utilization <= max_util:
                if allowed_tier is None:
                    return None, reason
                # Return cheapest of requested and allowed
                tier_order = ["small", "medium", "large"]
                effective_tier = tier_order[
                    min(
                        tier_order.index(requested_tier),
                        tier_order.index(allowed_tier),
                    )
                ]
                return effective_tier, reason
        return None, "Budget exhausted"

    def record_spend(self, cost: float):
        self.spent_today += cost


# Simulate requests over a day as budget fills
enforcer = BudgetEnforcer(daily_budget_usd=50.0)
tier_decisions = {"small": 0, "medium": 0, "large": 0, "blocked": 0}

rng2 = np.random.default_rng(seed=42)
for _ in range(2000):
    requested = rng2.choice(["small", "medium", "large"], p=[0.5, 0.3, 0.2])
    actual_tier, reason = enforcer.get_allowed_tier(requested)

    if actual_tier is None:
        tier_decisions["blocked"] += 1
    else:
        tier_decisions[actual_tier] += 1
        # Simulate actual cost for this request
        rates = TIER_COSTS[actual_tier]
        prompt_t = int(rng2.lognormal(6, 0.8))
        completion_t = int(rng2.lognormal(5, 0.7))
        cost = (prompt_t / 1000) * rates["input"] + (
            completion_t / 1000
        ) * rates["output"]
        enforcer.record_spend(cost)
Out[20]:
Console
Budget Enforcement Simulation (2,000 requests, $50 daily budget):

  Budget spent: $1.45 (2.9% utilized)

  Request routing decisions:
    small       1002 requests (50.1%)
    medium       605 requests (30.2%)
    large        393 requests (19.7%)
    blocked        0 requests (0.0%)

Cost Dashboards and Reporting

Effective cost dashboards present costs at the right granularity for each audience. Engineers need per-request and per-feature breakdowns to debug cost regressions. Finance teams need daily and monthly totals with trend projections. Product managers need cost-per-outcome metrics that connect spending to business value.

A useful dashboard structure organizes metrics into three layers:

Real-time layer: Current spend rate (USD/hour), active request count, GPU utilization, cache hit rates. Refreshes every minute. Surfaces anomalies and operational issues immediately.

Daily summary layer: Total spend, cost breakdown by feature and team, per-user percentiles (median, P95, P99), cost per token by model tier, comparison to prior day and prior week. Surfaces trends and allocation changes.

Monthly planning layer: Monthly spend totals, forecast vs. actuals, per-feature cost trends, optimization opportunity analysis, reserved instance utilization and coverage recommendations. Supports budget planning and infrastructure decisions.

In[21]:
Code
# Simulate a multi-day cost time series for dashboard illustration
days = 30
daily_costs = []
rng3 = np.random.default_rng(seed=42)

for d in range(days):
    # Organic growth trend of ~2% per day
    growth_factor = 1.02**d
    # Weekend dip (days 5-6, 12-13, etc.)
    day_of_week = d % 7
    weekday_factor = 0.65 if day_of_week >= 5 else 1.0
    # Random variation
    noise = rng3.normal(1.0, 0.05)
    base_daily = 180.0  # Base cost in USD per day
    daily_costs.append(base_daily * growth_factor * weekday_factor * noise)

# Compute rolling 7-day average
rolling_7d = []
for i in range(days):
    start = max(0, i - 6)
    rolling_7d.append(np.mean(daily_costs[start : i + 1]))

# Compute projected monthly total using last 7-day average rate
last_week_avg = np.mean(daily_costs[-7:])
monthly_projection = last_week_avg * 30

total_mtd = sum(daily_costs)
Out[22]:
Console
30-Day Cost Summary:

  Total (30 days): $6,612.60
  Average daily:   $220.42
  Peak daily:      $326.54 (day 30)
  Minimum daily:   $120.77 (day 6)
  Last 7-day avg:  $272.26/day
  Monthly projection (at current rate): $8,167.81
Out[23]:
Visualization
Bar chart of 30 simulated daily infrastructure costs with regular weekend dips, an upward-trending trailing 7-day average, and a horizontal 30-day average reference line.
Simulated daily infrastructure spend over 30 days with a trailing 7-day rolling average. Weekend dips are visible in the daily bars, while the rolling average reveals the underlying growth trend. This view helps distinguish expected business growth from a possible anomaly.

Cost Regression Detection

Cost regressions occur when code changes or configuration updates inadvertently increase cost per request. Without automated detection, they often go unnoticed until they accumulate into significant spending increases.

A cost regression can take several forms: a new prompt template that adds 200 tokens to every request, a code path that requests a larger model than necessary, a bug that disables prefix caching, or a new feature that generates much longer responses than expected.

Detecting regressions requires tracking per-feature cost metrics over time and alerting when they deviate significantly from baseline. The key metrics to track are average tokens per request (both input and output) and cost per request, broken down by feature. A step change in these metrics after a deployment is a strong signal of a cost regression.

Out[24]:
Visualization
Four-line chart of simulated average tokens per request by feature over 28 days. Summarization jumps by about 220 tokens after the deployment on day 15, while chat, autocomplete, and search remain near their baselines.
Average tokens per request by feature over a simulated 28-day period. A step change in the summarization feature on day 15 illustrates how a prompt-template change can cause a cost regression that is clearly visible when tracked per feature. Without per-feature tracking, this increase would be masked by aggregate statistics.