Skip to content

PATTERN Cited by 1 source

Live visualization of sampled metrics

Live visualization of sampled metrics is the pattern of rendering a streaming time-series directly inside the sampling process using an interactive plot library, for real-time monitoring without a separate metrics pipeline (no Prometheus, no Grafana, no storage backend).

(Source: sources/2026-04-21-planetscale-profiling-memory-usage-in-mysql.)

When to use

  • Ad-hoc investigation of a specific running workload (query, process, agent) where setting up a dashboard pipeline is overkill.
  • Laptop-to-production — one-off diagnostic sessions where the investigator already has a Python + matplotlib environment.
  • Pattern demonstration in blog posts / documentation where reproducibility matters more than scale.
  • Short-lived investigations — 10s of seconds to a few minutes of live data.

When not to use

  • Long-running monitoring — matplotlib interactive mode is not designed for hour-long sessions.
  • Multi-host collection — no built-in aggregation across sources.
  • Retention — the sliding window is in-process memory, lost on script exit.
  • Sharing — only visible on the monitoring host's screen.

Canonical shape (matplotlib + Python)

From the Dicken post:

import matplotlib.pyplot as plt

def configure_plot(self, plt):
    plt.ion()                          # interactive mode on
    fig = plt.figure(figsize=(12, 5))
    plt.stackplot(self.x, self.y, colors=self.color_sequence)
    plt.legend(labels=self.mem_labels,
               bbox_to_anchor=(1.04, 1),
               loc="upper left",
               borderaxespad=0)
    plt.tight_layout(pad=4)
    return fig

def draw_plot(self, plt):
    plt.clf()
    plt.stackplot(self.x, self.y, colors=self.color_sequence)
    plt.legend(labels=self.mem_labels, ...)
    plt.xlabel("milliseconds since monitor began")
    plt.ylabel("Kilobytes of memory")

# per-sample loop
c.execute(MEM_QUERY, (connection_id,))
results = c.fetchall()
self.update_xy_axis(results, frequency)
self.update_labels(results)
self.draw_plot(plt)
fig.canvas.draw_idle()
fig.canvas.start_event_loop(frequency / 1000)

Three mechanisms compose:

  1. plt.ion() — matplotlib's interactive mode. plt.draw() returns immediately instead of blocking on plt.show().
  2. plt.clf() + plt.stackplot() — clear and redraw each tick. Stacked because total memory = sum of categories.
  3. fig.canvas.start_event_loop(interval) — lets matplotlib process UI events (pan, zoom, close) while sleeping. Replaces time.sleep() so the plot stays responsive.

Legend cardinality reduction

Dicken's script uses an underscore-prefix trick to suppress small categories from the legend without dropping them from the stack:

# Only show top memory users in legend
if (usage < total_mem / 1024 / 50):
    mem_type = '_' + mem_type
self.mem_labels.insert(0, mem_type)

matplotlib's convention: labels starting with _ are excluded from plt.legend() output. Categories below the threshold still render in the stackplot but don't clutter the legend.

Sliding window via list ops

if (len(self.x) > 50):
    self.x.pop(0)
    for i in range(len(self.y)):
        self.y[i].pop(0)

Cheap for the 50-sample case; O(N) per tick. For larger windows a collections.deque(maxlen=N) would be O(1) amortised.

Caveats

  • Single-threaded event loop — matplotlib UI + sampling + DB query all run on the main thread. Heavy sampling queries freeze the UI.
  • Stackplot label order — in Dicken's script labels are inserted at position 0 each iteration (insert(0, ...)), so the legend order depends on iteration direction of results. Fragile.
  • Colour palette size cap — the 12-colour color_sequence wraps around beyond 12 categories; distinct categories will share colours.
  • Script bug — Dicken's final connection.close() references an undefined connection (should be database_connection). Minor but real.

Seen in

Last updated · 470 distilled / 1,213 read