Heatmaps in Grafana: Monitoring best practices explained
When you're troubleshooting a sudden spike in customer complaints about slow API responses, a single p99 metric tells you the worst case—but it won't tell you the story. Heatmaps in Grafana let you visualize the full distribution of latency across time, revealing patterns that percentile-only monitoring misses. In this guide, we'll walk through building heatmaps that expose the truth in your latency data.
Why heatmaps matter for latency visibility
Most teams rely on dashboard panels showing p50, p95, and p99 latencies. These snapshots are useful for SLA compliance, but they compress rich information into a few numbers. A heatmap, by contrast, shows you the density of requests at every latency bucket across your time range. This density visualization—often color-coded from cool to hot—makes it obvious when your traffic patterns shift or when your system develops new failure modes.
Consider a real scenario: your median response time stays stable at 150ms, your p99 bounces between 800ms and 1200ms, but customer complaints spike on certain hours. A heatmap would immediately show you whether those hours have a skewed distribution (tail heavy) or whether a secondary peak in latency emerged that wasn't there before.
Use histogram buckets
Heatmaps need bucketed data to work. In Prometheus, this comes from histogram metrics. A histogram is different from a counter or gauge—it records observations into predefined buckets and automatically maintains a sum and count.
When you instrument your application, use a histogram for request duration:
// Example: Node.js with prom-client
const http = require('http');
const promClient = require('prom-client');
const httpRequestDuration = new promClient.Histogram({
name: 'http_request_duration_seconds',
help: 'Duration of HTTP requests in seconds',
labelNames: ['method', 'route', 'status_code'],
buckets: [0.001, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2, 5, 10]
});
http.createServer((req, res) => {
const start = Date.now();
res.on('finish', () => {
const duration = (Date.now() - start) / 1000;
httpRequestDuration
.labels(req.method, req.url, res.statusCode)
.observe(duration);
});
// handle request
}).listen(3000);The buckets you choose matter. If your typical latency is 50–500ms, don't use buckets like [1s, 2s, 5s, 10s]—everything will stack in the first bucket. Instead, tune buckets to your expected range. The default buckets in most client libraries work well for general web services; adjust them based on your system's behavior.
Prometheus scrapes this histogram and automatically generates _bucket, _count, and _sum` metrics. Grafana's heatmap visualization uses the bucket data to render the distribution over time.
Spot bimodal latency
One of the most powerful uses of heatmaps is detecting bimodal distributions—when your latency has two distinct peaks instead of one smooth curve. This often signals that your system is handling two different types of requests or experiencing two different failure modes simultaneously.
Imagine an e-commerce API that handles both quick lookups (50–100ms) and heavy analytics queries (2–5s). Without a heatmap, you might see p50 at 150ms and p99 at 4s, which looks like normal variance. But a heatmap would show you two clear vertical bands of density—one around 80ms and another around 3s. This clarity lets you make smarter routing decisions, maybe sending analytics queries to a separate service or queue.
Here's what to look for in a Grafana heatmap:
- Single vertical band: normal, stable distribution.
- Two or more bands: bimodal or multimodal—investigate which code paths or customer cohorts cause each peak.
- Horizontal streaks at high latencies: transient spikes, possibly due to GC, lock contention, or network blips.
- Widening band over time: degradation, either in your system or in incoming traffic characteristics.
When you spot a bimodal pattern, the next step is to add a label dimension to your histogram. Carve out a separate metric for each candidate cause—by endpoint, by database shard, by request type. Then create separate heatmaps for each. You'll quickly isolate which code path owns which peak.
Better than single percentiles
Percentile metrics (p50, p95, p99) are easy to understand and easy to alert on. But they're also vulnerable to statistical illusions. If you have 1,000 requests and p99 represents the top 10, a single slow request can shift p99 by 10–20% even if the overall system behavior hasn't changed.
Heatmaps sidestep this problem by showing the density at each latency bucket. Instead of asking "what is p99?", a heatmap answers "how many requests fall into each latency range, and has that distribution changed?" This is more robust to noise and more informative for root cause analysis.
Furthermore, percentiles hide distribution shape. Two systems could have identical p50, p95, and p99 but very different user experiences if one has a smooth distribution and the other is bimodal. The heatmap exposes this instantly.
That said, don't abandon percentiles entirely. Use heatmaps for exploration and root cause work; use percentile alerts for on-call automation. The two complement each other.
Color scales for density
The visual encoding in a heatmap is crucial. Grafana offers several color schemes; the most effective ones follow a cool-to-hot gradient (blue to red) so your eye naturally tracks high-density areas.
When setting up a heatmap panel in Grafana:
- Select your histogram metric:
rate(http_request_duration_seconds_bucket[5m]). - Set the format to "Time series buckets" or "Prometheus histograms" depending on your Grafana version.
- Under "Display", choose a color scheme like "Interpolate" or "Spectrum" (not "Value gradient", which can obscure patterns).
- Set the Y-axis to logarithmic scale if your buckets span multiple orders of magnitude (e.g., 1ms to 10s).
- Adjust the color threshold so that normal traffic is cool (blue) and anomalies are warm (orange/red).
A well-tuned heatmap makes anomalies pop out visually. If your normal pattern is a consistent blue-green band and one day you see red streaks at 5s latency, that's a screaming signal something changed.
Use the "Legend" display option to show the bucket boundaries so viewers understand which region corresponds to which latency range. Pair the heatmap with a text annotation or dashboard variable showing SLA thresholds (e.g., "SLA is p99 < 500ms"), so teams can reason about whether the heatmap reveals compliance violations.
Common mistakes
Choosing too few or too many buckets: Too few (e.g., just [1s, 5s, 10s]) and everything clumps together. Too many (e.g., 50+ buckets) and the heatmap becomes noise. Start with 10–15 buckets covering your expected range; refine after observing real traffic.
Using linear scales for wide latency ranges: If your latencies span 1ms to 10s, a linear Y-axis compresses the millisecond range into a thin line. Switch to logarithmic scale to see all buckets clearly.
Forgetting to include status code or endpoint labels: A heatmap of all requests pooled together can hide important segmentation. If errors are slower than successes, or if one endpoint is degraded, an unlabeled heatmap won't show it. Create separate metrics or use Grafana's label grouping.
Setting color thresholds incorrectly: If your "hot" color doesn't kick in until 50,000 requests/sec but your normal traffic is 30,000/sec, nothing will ever look red. Calibrate your threshold to your system's normal peak, so anomalies stand out.
Ignoring time aggregation: If your heatmap query uses a very short time bucket (e.g., 10s), you might see too much noise; if too long (e.g., 1h), you'll miss transient spikes. Start with 1–5 minute buckets and adjust based on your incident response window.
Key takeaways
- Histograms are the foundation: use them for all latency-sensitive metrics and tune your buckets to your system's expected range.
- Heatmaps reveal distribution shape: bimodal, skewed, or degrading patterns are obvious in a heatmap but hidden in percentile summaries.
- Color density matters: a well-tuned cool-to-hot gradient makes anomalies pop visually, speeding up incident triage.
- Complement, don't replace, percentiles: heatmaps are for exploration and root cause; percentile alerts are for automation.
- Segment your data: label your histograms by endpoint, status, shard, or other dimensions so you can slice heatmaps to isolate failure modes.
- Iterate on bucket and color tuning: what works for one system won't work for all; observe production traffic and refine your heatmap config regularly.






Comments
Loading comments…