hoststat

package
v0.3.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 7, 2026 License: GPL-3.0 Imports: 15 Imported by: 0

Documentation

Overview

Package hoststat reads process-tree, network and GPU presence for soak telemetry. It does not signal processes.

Index

Constants

View Source
const (
	SpanYear  = 0
	SpanMonth = 1
	SpanWeek  = 2
	SpanDay   = 3
	SpanHour  = 4
	SpanSec   = 6
)

Timespan values match the Plex Dashboard bucket sizes: 6 second, 4 hour, 3 day, 2 ISO week, 1 month, 0 year. Resources only keeps per-second rows on the wire; coarser tiers exist so old samples stay bounded.

Variables

This section is empty.

Functions

func BucketStart

func BucketStart(t time.Time, span int, loc *time.Location) time.Time

BucketStart is the inclusive start of the timespan bucket that contains t in loc (the server's local zone).

func GPU

func GPU(devRoot string) string

GPU reports whether a render device is visible. Utilization is not inferred.

func NetDev

func NetDev(procRoot string) (rx, tx uint64, err error)

NetDev sums received and transmitted bytes across non-loopback interfaces.

Types

type FilesystemCapacity

type FilesystemCapacity struct {
	TotalBytes Metric[uint64] `json:"total_bytes"`
	// Available is space available to this user, not privileged free blocks.
	AvailableBytes Metric[uint64] `json:"available_bytes"`
}

type GPUProvider

type GPUProvider interface {
	SampleGPU(context.Context) ([]GPUReading, error)
}

type GPUReading

type GPUReading struct {
	Device             string          `json:"device"`
	Capabilities       []string        `json:"capabilities"`
	UtilizationPercent Metric[float64] `json:"utilization_percent"`
}

GPUReading is optional provider evidence, not inferred from device presence. Providers must supply bounded device IDs and verified capability strings.

type Metric

type Metric[T any] struct {
	Value *T          `json:"value"`
	At    time.Time   `json:"at"`
	State MetricState `json:"state"`
}

Metric retains the original observation time. Stale may have a nil Value when a formerly unlimited local constraint expires. Only Available values may be used for admission; Stale values are diagnostic, never free capacity.

type MetricState

type MetricState string

MetricState distinguishes an unknown reading from a measured zero. Unlimited means the configured cgroup explicitly has no local limit, not infinite host capacity.

const (
	Unavailable     MetricState = "unavailable"
	Available       MetricState = "available"
	Stale           MetricState = "stale"
	Unlimited       MetricState = "unlimited"
	MaxScratchPaths             = 16
	MaxGPUDevices               = 16
)

type NodeOptions

type NodeOptions struct {
	Now           func() time.Time
	Interval      time.Duration // default 5s; minimum 1s
	MaxAge        time.Duration // default 3 intervals; at least one interval
	SampleTimeout time.Duration // default 1s; at most one interval
	ScratchPaths  []string      // explicit absolute paths; no recursive scans
	CgroupV2Path  string        // optional explicit absolute cgroup directory, Linux only
	Sampler       NodeSampler
	GPU           GPUProvider
	Filesystem    func(context.Context, string) (FilesystemCapacity, error)
}

NodeOptions bounds retained data and sampling frequency. SampleTimeout is cooperative, not a syscall preemption guarantee: providers must honor context, and native filesystem sampling expects local paths. A hung remote mount may delay Run; cached Snapshot reads continue and age their observations.

type NodeReading

type NodeReading struct {
	HostCPUPercent           Metric[float64] `json:"host_cpu_percent"`
	ProcessCPUPercent        Metric[float64] `json:"process_cpu_percent"`
	HostMemoryTotalBytes     Metric[uint64]  `json:"host_memory_total_bytes"`
	HostMemoryAvailableBytes Metric[uint64]  `json:"host_memory_available_bytes"`
	ProcessRSSBytes          Metric[uint64]  `json:"process_rss_bytes"`
	// Cgroup fields describe ONLY the explicitly configured cgroup-v2 directory.
	// Ancestor limits/cpusets are not resolved: these are not effective pod capacity.
	CgroupMemoryLimitBytes   Metric[uint64]  `json:"cgroup_memory_limit_bytes"`
	CgroupMemoryCurrentBytes Metric[uint64]  `json:"cgroup_memory_current_bytes"`
	CgroupCPUQuotaCores      Metric[float64] `json:"cgroup_cpu_quota_cores"`
}

NodeReading separates measurement scopes. CPU percentages use total host CPU capacity as denominator; ProcessRSSBytes is this process only (no children). Host readings in a container describe the host view, not pod capacity.

type NodeResources

type NodeResources struct {
	// contains filtered or unexported fields
}

NodeResources holds exactly one snapshot, independently of Plex Resources. Construction does no I/O and starts no goroutines. The owner starts Run and cancels its context, then waits for Run to return before releasing providers.

func NewNodeResources

func NewNodeResources(opts NodeOptions) (*NodeResources, error)

func (*NodeResources) Run

func (r *NodeResources) Run(ctx context.Context) error

Run samples serially until cancellation. Callers own the goroutine and must wait for its return. Cancellation and SampleTimeout are cooperative; neither can preempt a native filesystem syscall or a provider that ignores context.

func (*NodeResources) Snapshot

func (r *NodeResources) Snapshot() NodeSnapshot

type NodeSampler

type NodeSampler interface {
	Sample(context.Context) (NodeReading, error)
}

NodeSampler and GPUProvider must honor ctx promptly and must not retain or mutate returned data. Run invokes providers serially (no spawned I/O goroutines). A provider that ignores context delays Run shutdown; it is never abandoned in a background goroutine. Snapshot never waits on a provider or performs I/O.

type NodeSnapshot

type NodeSnapshot struct {
	NodeReading
	Scratch  []ScratchSnapshot `json:"scratch"`
	GPU      []GPUReading      `json:"gpu"`
	GPUState MetricState       `json:"gpu_state"`
	GPUAt    time.Time         `json:"gpu_at"`
}

type Options

type Options struct {
	Now    func() time.Time
	Loc    *time.Location
	Sample func() (Sample, bool)
}

Options configure the sampler clock and the sample function (tests).

type Resources

type Resources struct {
	// contains filtered or unexported fields
}

Resources holds a bounded ring of host samples for this node. Clustered nodes each sample themselves; a client reads the node it connected to.

func NewResources

func NewResources(opts Options) *Resources

NewResources starts empty. Run samples once a second; tests call Add.

func (*Resources) Add

func (r *Resources) Add(s Sample)

Add records a sample into every kept timespan using a weighted average.

func (*Resources) Query

func (r *Resources) Query(span int, atMin, atMax int64, exact int64, hasExact, hasMin, hasMax bool) []Sample

Query returns samples for one kept timespan, oldest first. Unknown spans have no rows. atMin/atMax are inclusive unix seconds when the matching has* flag is set.

func (*Resources) Run

func (r *Resources) Run(ctx context.Context)

Run records one sample per second until ctx is done.

type Sample

type Sample struct {
	At         time.Time
	HostCPU    float64
	ProcessCPU float64
	HostMem    float64
	ProcessMem float64
	// contains filtered or unexported fields
}

Sample is one host/process observation at a bucket start. n is the number of observations folded into the averages and is not on the wire.

type ScratchSnapshot

type ScratchSnapshot struct {
	Path string `json:"path"`
	FilesystemCapacity
}

type Telemetry

type Telemetry struct {
	At           time.Time `json:"at"`
	RSSBytes     uint64    `json:"rss_bytes"`
	SelfRSS      uint64    `json:"self_rss_bytes"`
	HeapAlloc    uint64    `json:"heap_alloc"`
	HeapInuse    uint64    `json:"heap_inuse"`
	HeapSys      uint64    `json:"heap_sys"`
	HeapIdle     uint64    `json:"heap_idle"`
	HeapReleased uint64    `json:"heap_released"`
	GOGC         int       `json:"gogc"`
	FDs          int       `json:"fds"`
	SQLiteFDs    int       `json:"sqlite_fds"`
	Goroutines   int       `json:"goroutines"`
	FFmpeg       int       `json:"ffmpeg"`
	Sessions     int       `json:"sessions"`
	Transcodes   int       `json:"transcodes"`
	Children     int       `json:"children"`
	RetryQueue   int       `json:"retry_queue"`
	NetRx        uint64    `json:"net_rx_bytes"`
	NetTx        uint64    `json:"net_tx_bytes"`
	GPU          string    `json:"gpu"`
	Valid        bool      `json:"valid"`
	Error        string    `json:"error,omitempty"`
}

Telemetry is one soak sample. Session counts are supplied by the caller.

func Collect

func Collect(procRoot, devRoot string, pid, sessions, transcodes int) Telemetry

Collect reads the process tree, network counters and GPU presence.

type Tree

type Tree struct {
	RSSBytes     uint64
	SelfRSS      uint64
	FDs          int
	SQLiteFDs    int
	Children     int
	FFmpegPIDs   []int
	GuardianPIDs []int
}

Tree is one process and its descendants.

func ProcessTree

func ProcessTree(procRoot string, pid int) (Tree, error)

ProcessTree sums RSS and file descriptors for pid and its descendants. ffmpeg and encoder-guardian pids are listed separately and are included in the sums.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL