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:
plt.ion()— matplotlib's interactive mode.plt.draw()returns immediately instead of blocking onplt.show().plt.clf()+plt.stackplot()— clear and redraw each tick. Stacked because total memory = sum of categories.fig.canvas.start_event_loop(interval)— lets matplotlib process UI events (pan, zoom, close) while sleeping. Replacestime.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¶
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 ofresults. Fragile. - Colour palette size cap — the 12-colour
color_sequencewraps around beyond 12 categories; distinct categories will share colours. - Script bug — Dicken's final
connection.close()references an undefinedconnection(should bedatabase_connection). Minor but real.
Seen in¶
- PlanetScale's Profiling memory usage in MySQL (2024-04-11).
Canonical instance: live stackplot of per-thread memory
categories sampled at 500 ms intervals over a 100M-row
ORDER BYquery and aFULLTEXTindex build. (Source: sources/2026-04-21-planetscale-profiling-memory-usage-in-mysql.)