Documentation
¶
Overview ¶
Package imztop is a btop-style desktop resource monitor built on ImZero2 + egui2, consuming the in-repo `observability/sysmetrics` data layer. Wired as the imzero2 demo subcommand `appCode == 7`.
The package is read-only against sysmetrics. There is no process write-side (kill / nice / signal) by design — see ADR-0020 SD11.
Architecture ¶
One sampler goroutine owns a *sysmetrics.Bundle and ticks at SamplerOptions.UpdateInterval (default 1 s). Each tick it calls Bundle.Sample, appends the result to per-series ring buffers, then publishes a fresh PublishedSnapshot via atomic.Pointer. The egui frame loop reads the latest snapshot via atomic.Load and re-slices stable ring backing memory — no allocation on the hot path.
See also ¶
- doc/adr/0020-imzero2-imztop-resource-monitor.md — accepted decision and milestone plan.
- public/observability/sysmetrics/ — data source.
Index ¶
- Constants
- func EnterReplay(ctx context.Context, w sysmreplay.Window, opts StoreSourceOptions) (err error)
- func LeaveReplay() (err error)
- func StartReplay(ctx context.Context, w sysmreplay.Window, opts StoreSourceOptions)
- type App
- type BundleSourceI
- type NamedSeries
- type NamedValue
- type ProcSortByE
- type PublishedSnapshot
- type ReplayOptions
- type ReplaySampler
- func (inst *ReplaySampler) Close() (err error)
- func (inst *ReplaySampler) Exhausted() (done bool)
- func (inst *ReplaySampler) Interval() (d time.Duration)
- func (inst *ReplaySampler) IsPaused() (p bool)
- func (inst *ReplaySampler) Latest() (snap *PublishedSnapshot)
- func (inst *ReplaySampler) Pause(p bool)
- func (inst *ReplaySampler) Position() (at time.Time, ok bool)
- func (inst *ReplaySampler) Seek(t time.Time)
- func (inst *ReplaySampler) SeekWindow(from, to time.Time)
- func (inst *ReplaySampler) SetSpeed(x float64)
- func (inst *ReplaySampler) Speed() (x float64)
- func (inst *ReplaySampler) Start(ctx context.Context)
- func (inst *ReplaySampler) Step(n int)
- func (inst *ReplaySampler) Window() (w sysmreplay.Window)
- type ReplayStateE
- type ReplayStatus
- type Sampler
- type SamplerI
- type SamplerOptions
- type SlidingWindow
- type StoreSource
- func (inst *StoreSource) All(ctx context.Context, w sysmreplay.Window) iter.Seq2[*sysmsnap.BundleSnapshot, error]
- func (inst *StoreSource) Close()
- func (inst *StoreSource) Coverage(ctx context.Context, w sysmreplay.Window, bucket time.Duration) (runs []sysmreplay.CoverageRun, err error)
- func (inst *StoreSource) Decimation() (stored int64, shown int, ok bool)
- func (inst *StoreSource) Endpoint() (url string)
- func (inst *StoreSource) Host() (host string)
- func (inst *StoreSource) Preview(ctx context.Context, w sysmreplay.Window, bucket time.Duration) (points []sysmreplay.PreviewPoint, err error)
- type StoreSourceOptions
Constants ¶
const ( // DefaultReplaySpeed plays history at the rate it was recorded. DefaultReplaySpeed = 1.0 // MaxReplayGap caps how long the transport waits between two consecutive // bundles. Stored history has gaps — the tee is opt-in, it drops bundles // under back-pressure, and the scraper stops when the box does — and // honouring one literally would park replay for the length of the outage. // The gap is compressed to this, so a break in history reads as a pause in // the plots rather than as a hung UI. MaxReplayGap = 2 * time.Second )
Defaults for ReplayOptions.
Variables ¶
This section is empty.
Functions ¶
func EnterReplay ¶ added in v0.0.20
func EnterReplay(ctx context.Context, w sysmreplay.Window, opts StoreSourceOptions) (err error)
EnterReplay opens the session synchronously and makes it the active sampler.
It blocks on a network round trip and must not be called from the render thread; StartReplay is the safe form. Exported for a caller that already owns a background goroutine.
func LeaveReplay ¶ added in v0.0.20
func LeaveReplay() (err error)
LeaveReplay ends the session and returns every window to live data. It is safe to call when no session is open, and safe to call while one is opening — the open then discards itself rather than installing.
func StartReplay ¶ added in v0.0.20
func StartReplay(ctx context.Context, w sysmreplay.Window, opts StoreSourceOptions)
StartReplay opens a replay session in the background and returns immediately.
It is the entry point a UI uses: opening dials ClickHouse and verifies the schema, which blocks for as long as the network takes, and the render thread cannot afford that. Progress is read with CurrentReplayStatus. Calling it while a session is opening or open is a no-op.
Types ¶
type App ¶
type App struct {
// contains filtered or unexported fields
}
App is the per-window imztop instance. The registry's factory ctor allocates a fresh App per Open() so two windows have independent UI state: the selected network interface, the process-panel sort key, filter and tree toggle, the Proc Map's area metric, and the panel widgets' own persistent state.
func (*App) Frame ¶
func (inst *App) Frame(ctx app.FrameContextI) (err error)
Frame renders one frame of the imztop window body. The host has already pre-pushed a window-unique salt onto inst.ids via c.IdScope (windowhost.renderWindowBody), so widget ids derived from inst.ids are scoped under that salt — no further package-level coordination is needed (every render helper is a method on *App).
type BundleSourceI ¶ added in v0.0.20
type BundleSourceI interface {
All(ctx context.Context, w sysmreplay.Window) iter.Seq2[*sysmsnap.BundleSnapshot, error]
}
BundleSourceI is where a ReplaySampler gets its bundles: one window of stored history, ascending in time. sysmreplay.Reader implements it.
It is an interface so the transport can be exercised against a synthetic window with no database, which is the only way the pacing and stepping behaviour is testable at all.
type NamedSeries ¶
NamedSeries is one labelled history series in a published snapshot (e.g. "sda" → read rates over the last 10 minutes). The slice is a fresh copy of the sampler's ring backing memory; mutating it does not affect future ticks.
type NamedValue ¶
NamedValue is a (key, value) pair for namedWindowSet.push.
type ProcSortByE ¶
type ProcSortByE uint8
ProcSortByE selects which column the process panel sorts on.
const ( // ProcSortByCPU keys on the EWMA-smoothed CPU% (sampler's // ProcCPUSmoothed slice). Default sort key: order is stable // across transient spikes, matching the column the heatmap palette // is tinted onto. ProcSortByCPU ProcSortByE = iota // ProcSortByCPURaw keys on the raw sampler-interval CPU% value. // Useful when the user is hunting transient spikes that the // smoothed view dampens. When selected, the raw column is tinted // instead of the smoothed one. ProcSortByCPURaw ProcSortByMem ProcSortByPID ProcSortByUser ProcSortByName )
type PublishedSnapshot ¶
type PublishedSnapshot struct {
SampledAtUnixMs int64
// HistoryEpoch identifies the continuous run of samples the History*
// slices belong to. It changes when the frames stop continuing the
// previous ones — a replay seeking to another range (ADR-0197 §SD12), or
// the render path switching between the live fold and a replay one — and
// never within a run.
//
// A consumer that accumulates across frames rather than reading the
// snapshot whole (the CPU heatmap's scrolling ring is the one) must
// compare it and rebuild when it moves. Comparing timestamps cannot serve:
// a seek moves them in either direction and may overlap the range already
// drawn.
HistoryEpoch uint64
HistoryTimeUnixSec []float64
HistoryCPUTotal []float64
HistoryMemUsed []float64
HistoryDiskRead []float64 // MiB/s (sum across block devices)
HistoryDiskWrite []float64 // MiB/s
HistoryNetRx []float64 // MiB/s (sum across interfaces)
HistoryNetTx []float64 // MiB/s
HistoryBatteryPct []float64
HistoryCPUPerCore [][]float64 // [core][time] percent
HistoryGPUBusyPerDev [][]float64 // [device][time] percent
HistoryDiskReadByDev []NamedSeries // MiB/s per block device, ordered by name
HistoryDiskWriteByDev []NamedSeries // MiB/s per block device
HistoryNetRxByIface []NamedSeries // MiB/s per interface
HistoryNetTxByIface []NamedSeries // MiB/s per interface
LatestCPU *sysmsnap.CPUSnapshot
LatestMem *sysmsnap.MemSnapshot
LatestDisk *sysmsnap.DiskSnapshot
LatestNet *sysmsnap.NetSnapshot
LatestBattery *sysmsnap.BatterySnapshot
LatestGPU *sysmsnap.GPUSnapshot
LatestContainer *sysmsnap.ContainerInfo
LatestPSI *sysmsnap.PSISnapshot
Sensors []sysmsnap.TempReading
Procs []sysmsnap.ProcInfo
// ProcCPUSmoothed is the per-process EWMA-smoothed CPU% (α=
// procCPUEWMAAlpha), parallel to Procs by index. Drives the
// process-table sort key and the CPU%-cell background tint;
// the raw Procs[i].CPUPercent stays untouched so the displayed
// value still reflects the latest sampler-interval average.
// Smoothing is per-PID and persists across ticks via the
// Sampler's procCPUEWMA map (evicted when a PID disappears).
ProcCPUSmoothed []float32
// Topology is the static CPU containment hierarchy, delivered on the metric
// plane (ADR-0090 SD6) rather than read in-process by the consumer. nil until
// the first topology-bearing snapshot arrives (or if the scraper could not
// read it); the topology panel builds its treemap from it once.
Topology *sysmsnap.Topology
Errors map[sysmsnap.Domain]error
}
PublishedSnapshot is the read-only frame the renderer consumes. Built once per Sampler tick and replaced atomically; slices are owned by the snapshot and never mutated after publication, so concurrent readers see a coherent view.
All byte/sec history fields (disk/net) are stored in MiB/s so the renderer can label plot axes "MiB/s" without per-frame scaling. Sub-MiB/s rates appear as small fractional values; raw counters at the byte level remain available on the Latest* fields.
type ReplayOptions ¶ added in v0.0.20
type ReplayOptions struct {
// Source supplies the stored bundles.
Source BundleSourceI
// Window is the range to replay. Seek reopens the source with From moved.
Window sysmreplay.Window
// Sampler sizes the history windows the bundles fold into, exactly as for
// a live Sampler.
Sampler SamplerOptions
// Speed multiplies playback rate: 1 is as recorded, 2 twice as fast, 0.5
// half. Zero takes DefaultReplaySpeed.
Speed float64
// StartPaused holds the transport at the first bundle until Pause(false)
// or Step.
StartPaused bool
Log zerolog.Logger
}
ReplayOptions configures a ReplaySampler. Source is required.
type ReplaySampler ¶ added in v0.0.20
type ReplaySampler struct {
// contains filtered or unexported fields
}
ReplaySampler is the SamplerI that plays stored history (ADR-0197 §SD1).
It is a second *source* into the fold a live Sampler already has, not a subtype of one: the sliding windows, the per-process EWMA and the published snapshot are the same code, reached through the same onBundle. What differs is where the bundles come from and that they arrive on a transport the user drives.
Two clocks ¶
The bundles carry the timestamps they were recorded with, so the plot time axis and the observed-cadence readout report the historical run rather than this playback (ADR-0197 §SD3). Wall-clock pacing is therefore free to mean speed, and the two never have to be reconciled.
Off the render thread ¶
Reading the store blocks. Every read happens on the goroutine Start spawns, and the renderer only ever touches the atomically-published snapshot the fold already gives it — Latest is as cheap here as it is live.
func ActiveReplay ¶ added in v0.0.20
func ActiveReplay() (session *ReplaySampler)
ActiveReplay returns the running session, or nil when replay is not on. A UI drives the transport through it.
func NewReplaySampler ¶ added in v0.0.20
func NewReplaySampler(opts ReplayOptions) (inst *ReplaySampler, err error)
NewReplaySampler validates opts and returns a sampler that is not yet running; call Start.
func (*ReplaySampler) Close ¶ added in v0.0.20
func (inst *ReplaySampler) Close() (err error)
Close stops the transport and waits for the goroutine to finish.
func (*ReplaySampler) Exhausted ¶ added in v0.0.20
func (inst *ReplaySampler) Exhausted() (done bool)
Exhausted reports whether the window has been played to its end. The transport parks there rather than closing, so Seek can restart it.
func (*ReplaySampler) Interval ¶ added in v0.0.20
func (inst *ReplaySampler) Interval() (d time.Duration)
Interval reports the cadence of the recorded run, not of this playback: it comes from the fold, which derives it from consecutive bundles' own stamps (ADR-0197 §SD3).
func (*ReplaySampler) IsPaused ¶ added in v0.0.20
func (inst *ReplaySampler) IsPaused() (p bool)
IsPaused reports whether the transport is stopped.
func (*ReplaySampler) Latest ¶ added in v0.0.20
func (inst *ReplaySampler) Latest() (snap *PublishedSnapshot)
Latest returns the most recently folded frame, or nil before the first.
func (*ReplaySampler) Pause ¶ added in v0.0.20
func (inst *ReplaySampler) Pause(p bool)
Pause stops or resumes the transport.
func (*ReplaySampler) Position ¶ added in v0.0.20
func (inst *ReplaySampler) Position() (at time.Time, ok bool)
Position reports the stamp of the most recently folded bundle. ok is false before the first one.
func (*ReplaySampler) Seek ¶ added in v0.0.20
func (inst *ReplaySampler) Seek(t time.Time)
Seek restarts the transport at t, which becomes the window's lower bound. The fold is emptied first, so the plots redraw from the new range alone (ADR-0197 §SD12).
func (*ReplaySampler) SeekWindow ¶ added in v0.0.20
func (inst *ReplaySampler) SeekWindow(from, to time.Time)
SeekWindow restarts the transport over a new range, moving both bounds. It is what the range controls need: picking another stretch — by brushing the availability strip or by jogging — changes where the replay ends as well as where it starts.
The fold is emptied on the transport goroutine before the new range is read, so every control that lands here resets the plots the same way (§SD12). Position reads as unknown until the first bundle of the new range folds.
func (*ReplaySampler) SetSpeed ¶ added in v0.0.20
func (inst *ReplaySampler) SetSpeed(x float64)
SetSpeed changes the playback multiplier. Values <= 0 are ignored.
func (*ReplaySampler) Speed ¶ added in v0.0.20
func (inst *ReplaySampler) Speed() (x float64)
Speed reports the playback multiplier.
func (*ReplaySampler) Start ¶ added in v0.0.20
func (inst *ReplaySampler) Start(ctx context.Context)
Start launches the transport goroutine. Calling it twice is a no-op after the first.
func (*ReplaySampler) Step ¶ added in v0.0.20
func (inst *ReplaySampler) Step(n int)
Step advances n bundles while paused, ignoring pacing. It is a no-op when n <= 0 and has no effect while playing, where the transport is already advancing.
func (*ReplaySampler) Window ¶ added in v0.0.20
func (inst *ReplaySampler) Window() (w sysmreplay.Window)
Window reports the range being replayed.
type ReplayStateE ¶ added in v0.0.20
type ReplayStateE uint8
ReplayStateE is where the process-wide replay session has got to.
const ( // ReplayOff is live data: no session, no connection. ReplayOff ReplayStateE = iota // ReplayOpening means a session is being opened on its own goroutine. // Opening dials ClickHouse, so it is never instant and never on the render // thread. ReplayOpening // ReplayOn means the session is playing, paused, or parked at its end. ReplayOn // ReplayFailed means opening did not succeed; the status carries why. ReplayFailed )
func (ReplayStateE) String ¶ added in v0.0.20
func (inst ReplayStateE) String() (s string)
type ReplayStatus ¶ added in v0.0.20
type ReplayStatus struct {
State ReplayStateE
// Err is set only in ReplayFailed.
Err error
// Host and Endpoint say whose history this is and where it came from. Set
// from ReplayOn onwards.
Host string
Endpoint string
// Empty distinguishes the two ways a replay shows nothing: the window held
// no bundles at all (this is true) versus playback not having reached the
// first one yet (false). It is not an error — a host the tee never ran for
// is simply a host with no history — and the UI has to say so rather than
// leave the panels reading "waiting for first sample".
Empty bool
}
ReplayStatus is what a renderer needs to describe the session without touching it. It is a copy taken under the session lock, so it is safe to hold across a frame.
func CurrentReplayStatus ¶ added in v0.0.20
func CurrentReplayStatus() (st ReplayStatus)
CurrentReplayStatus reports the session for a renderer. It never blocks on the session's goroutine.
type Sampler ¶
type Sampler struct {
// contains filtered or unexported fields
}
Sampler runs a goroutine that periodically calls Bundle.Sample and publishes a PublishedSnapshot via atomic.Pointer.
func NewSampler ¶
func NewSampler(opts SamplerOptions, bus app.BusI) (inst *Sampler, err error)
NewSampler builds a pure-consumer Sampler (ADR-0090): it subscribes to the system-metrics plane on the host-provided bus and folds what arrives into sliding-window history + per-process EWMA. The /proc reader is a separate scraper (the carousel host's co-located one, the tour's, or an external sysmetricsd), so imztop builds no collectors and holds no system-state capability. bus is MountCtx.Bus() in the app; tests and the tour pass an inprocbus client fed by StartScraper. A nil bus degrades to NoopBus.
func (*Sampler) Interval ¶
Interval returns the most recent observed sample cadence (the scraper's real rate; see intervalNs). There is no setter — imztop does not control the cadence; it observes it from consecutive samples (ADR-0090 SD5).
func (*Sampler) Latest ¶
func (inst *Sampler) Latest() (snap *PublishedSnapshot)
type SamplerI ¶
type SamplerI interface {
Start(ctx context.Context)
Latest() (snap *PublishedSnapshot)
Pause(p bool)
IsPaused() (p bool)
// Interval reports the most recent observed sample cadence. Formatting is
// the caller's — the top bar renders it with String.
Interval() (d time.Duration)
Close() (err error)
}
SamplerI is the type the render path takes: a source of PublishedSnapshot frames, the freeze control, and the observed cadence the top bar and the CPU heatmap read.
Sampler is the live implementation, fed by a subscription to the metric plane. The render path takes the interface rather than the concrete type because ADR-0197 adds a second implementation that replays stored history from the ADR-0184 record store; everything downstream of Latest is indifferent to which one it is holding.
type SamplerOptions ¶
SamplerOptions configures a Sampler.
type SlidingWindow ¶
type SlidingWindow[T any] = slidingwindow.Window[T]
SlidingWindow aliases the shared observability sliding-window buffer, lifted out of imztop + imzrt per ADR-0061 SD13. See slidingwindow.Window for semantics (memmove-on-full, stable backing, per-tick copy-out; not safe for concurrent use).
func NewSlidingWindow ¶
func NewSlidingWindow[T any](capacity int32) *SlidingWindow[T]
NewSlidingWindow constructs a SlidingWindow holding at most capacity values (clamped to a minimum of 1).
type StoreSource ¶ added in v0.0.20
type StoreSource struct {
// contains filtered or unexported fields
}
StoreSource is the production BundleSourceI: stored history read from `boxer.facts` through the ADR-0184 record store.
This is the one place imztop reaches a database, and the reason ADR-0197 §SD7 is a decision rather than a detail — the app that ADR-0090 made capability-free now dials something. The connection is opened when a user enters replay, not at Mount, so an imztop nobody replays makes no connection at all.
func ActiveReplaySource ¶ added in v0.0.20
func ActiveReplaySource() (src *StoreSource)
ActiveReplaySource returns the running session's store source, or nil. The availability strip needs it to query coverage.
func NewStoreSource ¶ added in v0.0.20
func NewStoreSource(ctx context.Context, opts StoreSourceOptions) (inst *StoreSource, err error)
NewStoreSource dials ClickHouse and binds the replay reader to it.
Every failure it can hit means "there is nothing to replay", and each says which one it was, because they call for different actions from whoever reads the message: an unreachable server is a deployment problem, and a table that does not match is a tee that has never run here.
func (*StoreSource) All ¶ added in v0.0.20
func (inst *StoreSource) All(ctx context.Context, w sysmreplay.Window) iter.Seq2[*sysmsnap.BundleSnapshot, error]
All satisfies BundleSourceI, decimating the window when it holds more bundles than the fold can show (ADR-0197 §SD6, closed by §SD11).
The decision is made here rather than by the caller because this is the only layer that can ask the store how much is in there, and it runs on the transport's goroutine where a blocking count is affordable. A window inside the budget is read exactly as before.
Decimation samples whole recorded bundles rather than aggregating them, so a long range loses resolution and nothing else — see the sysmreplay package's decimation notes for why a bundle is not a thing that can be averaged.
func (*StoreSource) Close ¶ added in v0.0.20
func (inst *StoreSource) Close()
Close releases the store. The source is unusable afterwards.
func (*StoreSource) Coverage ¶ added in v0.0.20
func (inst *StoreSource) Coverage(ctx context.Context, w sysmreplay.Window, bucket time.Duration) (runs []sysmreplay.CoverageRun, err error)
Coverage reports where stored history is, for the availability strip (ADR-0197 §SD9). It blocks on a database read; call it off the render thread.
func (*StoreSource) Decimation ¶ added in v0.0.20
func (inst *StoreSource) Decimation() (stored int64, shown int, ok bool)
Decimation reports the last plan: how many bundles the window holds and how many are being replayed. ok is false when the last read was not decimated.
func (*StoreSource) Endpoint ¶ added in v0.0.20
func (inst *StoreSource) Endpoint() (url string)
Endpoint reports the ClickHouse URL, for the status line that has to explain where the history came from.
func (*StoreSource) Host ¶ added in v0.0.20
func (inst *StoreSource) Host() (host string)
Host reports the token being replayed.
func (*StoreSource) Preview ¶ added in v0.0.20
func (inst *StoreSource) Preview(ctx context.Context, w sysmreplay.Window, bucket time.Duration) (points []sysmreplay.PreviewPoint, err error)
Preview reports mean CPU busy per bin, for the strip's load layer (ADR-0197 §SD9). It blocks on a section read; call it off the render thread.
type StoreSourceOptions ¶ added in v0.0.20
type StoreSourceOptions struct {
// Host is the token whose series to replay. Empty takes the local
// hostname, sanitized the way the scraper sanitizes it.
//
// That default is right for the co-located deployment, where the scraper
// runs beside the GUI. It is wrong when the plane is bridged from an
// external sysmetricsd on another box, which publishes under its own
// token: imztop's live consumer is handed only the snapshot and never sees
// the subject it arrived on, so it cannot learn the token from the plane
// (ADR-0184 §SD8 records that gap). Picking a host is deferred; setting
// this is the workaround until it lands.
Host string
// Endpoint overrides the ClickHouse URL the CLICKHOUSE_* registry entries
// resolve to. Empty takes the registry's.
//
// It exists because the registry caches each variable on first read, so a
// process that has already resolved the endpoint cannot be repointed by
// changing the environment — a caller wanting history from a different
// server has to say so here.
Endpoint string
Log zerolog.Logger
}
StoreSourceOptions configures NewStoreSource.
Source Files
¶
- app_register.go
- doc.go
- imztop.go
- imztop_cpu_heatmap.go
- imztop_layout.go
- imztop_panel_availability.go
- imztop_panel_battery.go
- imztop_panel_cpu.go
- imztop_panel_disk.go
- imztop_panel_gpu.go
- imztop_panel_mem.go
- imztop_panel_net.go
- imztop_panel_pressure.go
- imztop_panel_proc.go
- imztop_panel_procmap.go
- imztop_panel_replaybar.go
- imztop_panel_sensors.go
- imztop_panel_topbar.go
- imztop_panel_topology.go
- imztop_plot.go
- imztop_procmap_tree.go
- imztop_rate_plot.go
- imztop_replay.go
- imztop_replay_coverage.go
- imztop_replay_session.go
- imztop_replay_source.go
- imztop_sampler.go
- imztop_sampler_keyed.go
- imztop_slidingwindow.go
- imztop_theme.go
- imztop_topology_tree.go
- imztop_tour.go
- imztop_tour_replay.go
- imztop_tour_synth.go
- imztop_ytalbot.go