Job Queue Depth Monitor — Live Backlog Trend, Threshold Status, and Drain Estimate

Job Queue Depth Monitor — Live Backlog Trend with Threshold Alerts · Dashboards · Plain HTML, CSS & JS · Live preview

What's included

Features

Live-updating depth, processing rate, and inline SVG sparkline trend, all on a shared polling interval
Single shared classify() function drives both the status badge color and the sparkline stroke color, preventing them from ever disagreeing
Honest "stalled" state for the drain-time estimate instead of showing a misleading huge or infinite number when processing rate is near zero
Fixed-length rolling history array keeps the sparkline's time window and Y-axis scale correctly bounded and up to date
Pause control genuinely stops the underlying polling interval, not just the visual display
Threshold legend directly visible on the tile, so the meaning of each status color is never left ambiguous
Tabular figure styling on numeric values prevents the layout from jittering as digit counts change

About this UI Snippet

Job Queue Depth Monitor — Building a Trustworthy Live Backlog Indicator

Screenshot of the Job Queue Depth Monitor — Live Backlog Trend with Threshold Alerts snippet rendered live

A raw "jobs in queue: 340" number tells an on-call engineer very little on its own — is 340 normal, or is it climbing toward an incident? This snippet builds a genuine monitoring tile: live depth, a processing rate, a trend sparkline, a threshold-derived health status, and — the trickiest part to get right — an honest estimate of how long the backlog will take to drain, one that refuses to show a misleading number when the math doesn't actually support one.

One shared `classify()` function, not two independently-checked threshold sets

Both the status label's color (healthy/elevated/critical) and the sparkline's stroke color are derived from calling the exact same classify(currentDepth) function and looking up the result in STATUS_LABEL or STROKE_COLOR respectively. If the threshold logic were instead duplicated — one if chain for the status badge, a separate one for the sparkline color — the two could silently drift apart over time as one gets updated and the other doesn't, showing a dashboard where the badge says "Critical" but the sparkline still renders green. Having exactly one classification function used everywhere makes that class of bug impossible.

Why the drain-time estimate can show "stalled" instead of a number

The naive formula for "time to drain" is depth / processingRate — but if the processing rate is at or near zero (the queue's workers are stuck, crashed, or simply not keeping up at all), that formula produces either a wildly enormous number or a division-by-zero Infinity. Rather than displaying either of those (both actively misleading to someone glancing at a dashboard during an incident, who might read "47000s" and assume the number is simply large-but-finite rather than realizing the queue isn't draining at all), the code explicitly checks whether the processing rate is meaningfully above zero and shows "stalled" when it isn't — an honest, immediately-understandable signal that no genuine estimate is currently possible.

The sparkline redraws from a fixed-length rolling history array

history is a fixed-size array (HISTORY_LEN entries) that tick() updates with push/shift — pushing the newest depth reading onto the end and shifting the oldest one off the front, keeping the array's length constant forever. renderSpark() rebuilds the entire SVG polyline from this array on every tick rather than trying to incrementally append a single new point to existing SVG markup — simpler to reason about, and correctly handles the sparkline's shared Y-axis scale needing to be recalculated (Math.max(...history, 500)) every time the visible window of data changes.

Pausing stops polling but doesn't hide anything already shown

The pause button toggles an actual setInterval/clearInterval pair rather than merely hiding updates client-side — this matters for a real implementation where tick() would be replaced with an actual network request to a metrics backend; pausing here genuinely stops that polling traffic rather than just freezing the display while continuing to silently fetch data in the background.

Build with AI

Build, Understand, Optimize, and Extend It With AI

Ask an AI assistant to explain why showing "stalled" is more honest and useful than displaying a technically-computed but enormous drain-time number, and to discuss what other dashboard metrics commonly suffer from this same "divide by a near-zero rate" problem. It's also worth asking for a version that adds a browser notification or audible alert when the status first crosses into critical, or one that shows separate sparklines for arrival rate and processing rate side by side to make the actual imbalance driving backlog growth more visible.

Prompt to recreate it

Copy this into your AI assistant of choice to build the effect from scratch, or as a jumping-off point for your own variant:

text
Build a live job-queue depth monitoring tile in HTML, CSS, and vanilla JavaScript — no external chart library, use inline SVG for the trend line.

Requirements:
- A tile showing the current queue depth (number of jobs waiting), a current processing rate, and an estimated drain time, all updating on a fixed polling interval (simulate realistic queue dynamics with randomized arrival and processing rates that sometimes cause the backlog to grow and sometimes shrink).
- Define numeric thresholds for "healthy," "elevated," and "critical" queue depth exactly once, in a single function, and use that same function's result to color BOTH a status badge label AND an inline SVG sparkline trend line showing recent depth history — the two must never be able to show conflicting status colors.
- Compute the drain-time estimate as depth divided by processing rate, but explicitly detect when the processing rate is at or near zero and show a distinct "stalled" state instead of a computed number in that case — do not show an extremely large or infinite numeric estimate, since that would be misleading to someone monitoring the dashboard during an incident.
- Maintain the sparkline's historical data as a fixed-length rolling array (push newest, shift oldest) so memory use and the visible time window stay bounded regardless of how long the page stays open, redrawing the full SVG polyline from that array on every update.
- Include a pause/resume control that actually stops and restarts the underlying polling interval (not just the visual display), so the pattern is clear for a real implementation that would be polling a live backend.
- Show a small legend clarifying the numeric threshold that defines each status color.

Want to tighten it up first? Run this prompt through the AI Prompt Studio to score it across 8 quality dimensions, catch anti-patterns, and tune the wording for Claude, ChatGPT, or Gemini before you paste it in.

Source Code

<div class="demo">
  <div class="queue-card">
    <div class="queue-header">
      <div>
        <span class="queue-title">email-delivery queue</span>
        <span class="queue-status" id="queueStatus">Healthy</span>
      </div>
      <button class="queue-pause" id="queuePauseBtn">Pause updates</button>
    </div>

    <div class="queue-stats">
      <div class="queue-stat">
        <span class="queue-stat-value" id="queueDepthValue">0</span>
        <span class="queue-stat-label">Jobs waiting</span>
      </div>
      <div class="queue-stat">
        <span class="queue-stat-value" id="queueRateValue">0/s</span>
        <span class="queue-stat-label">Processing rate</span>
      </div>
      <div class="queue-stat">
        <span class="queue-stat-value" id="queueEtaValue">—</span>
        <span class="queue-stat-label">Est. drain time</span>
      </div>
    </div>

    <svg class="queue-spark" id="queueSpark" viewBox="0 0 300 70" preserveAspectRatio="none"></svg>

    <div class="queue-thresholds">
      <span class="thresh healthy">● Healthy &lt; 200</span>
      <span class="thresh warn">● Elevated &lt; 500</span>
      <span class="thresh crit">● Critical ≥ 500</span>
    </div>
  </div>
</div>

Step by step

How to Use

  1. 1
    Watch the tile update liveDepth, processing rate, and the trend sparkline all refresh on a fixed interval, simulating realistic queue backlog dynamics.
  2. 2
    Watch the status badge and sparkline color change togetherBoth derive from the same shared classify() function and threshold values, so they always agree with each other as the depth crosses into elevated or critical territory.
  3. 3
    Watch the drain-time estimate during a stallWhen processing rate drops near zero, the estimate switches to "stalled" instead of showing a misleadingly large or infinite number.
  4. 4
    Click "Pause updates"Stops the polling interval entirely (not just hiding the display) — useful when you want to inspect a specific reading without it changing underneath you.
  5. 5
    Replace tick() with a real metrics pollSwap the simulated arrival/processing rate math for an actual fetch to your queue backend's metrics endpoint on the same interval.

Real-world uses

Common Use Cases

DEVOPS
Background job queue dashboards
Monitoring email delivery, image processing, or webhook-dispatch queues where backlog growth signals a real operational problem.
ONCALL
On-call and incident response tooling
A trustworthy, at-a-glance backlog indicator is exactly the kind of tile an on-call engineer needs during an active incident.
INFRA
Message broker and worker pool monitoring
Any system with a producer/consumer imbalance (Kafka consumer lag, SQS queue depth, Redis job queues) benefits from this same pattern.
CAPACITY
Capacity planning dashboards
Trend sparklines over time help identify whether a queue's backlog is a temporary spike or a sustained capacity shortfall.
Related: Keyboard Focus Order Debugger — Numbered Tab-Order Overlay
See the Keyboard Focus Order Debugger — Numbered Tab-Order Overlay for a related dashboards pattern worth pairing with this one.

Got questions?

Frequently Asked Questions

If the threshold logic for "what counts as critical" were duplicated in two places — once for the badge, once for the sparkline — they could silently drift out of sync as one gets updated without the other. A single shared classification function used by both guarantees they always agree.

The estimate formula (depth divided by processing rate) produces a misleadingly huge or infinite value whenever the processing rate is at or near zero. Showing "stalled" instead is a deliberately honest signal that no genuine time estimate is currently possible, rather than a technically-computed but practically meaningless number.

It stops the actual polling interval (clearInterval), not just the visual updates. In a real implementation where the tick function fetches from a live metrics API, pausing genuinely stops that network traffic rather than continuing to poll silently in the background.

It's recalculated on every render as the maximum value currently present in the rolling history array (with a floor of 500 so the chart doesn't look artificially dramatic during genuinely low-traffic periods), so the visible scale always fits whatever range of values is currently in the trailing window.

Replace the simulated arrivalRate/processingRate math inside tick() with an actual fetch call to your queue system's metrics endpoint, reading its real current depth and jobs-processed-per-second, keeping everything else (history tracking, classification, rendering) unchanged.

A fixed-length rolling window keeps memory use constant regardless of how long the dashboard stays open, and keeps the sparkline focused on a consistent, recent time range rather than compressing an ever-larger dataset into the same visual width over time.