Help us shape the future of Go! Take the 2026 Go Developer Survey

serviceuptime

package
v1.3.104 Latest Latest
Warning

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

Go to latest
Published: Oct 31, 2026 License: AGPL-3.0 Imports: 19 Imported by: 0

Documentation

Overview

Package serviceuptime pkg/serviceuptime/api.go c2-net-discovery that surface the Recorder's history. Exported as plain http.HandlerFunc so callers can wrap with chi (services) or gin (the visor's logserver) without dragging either framework into this package.

Package serviceuptime pkg/serviceuptime/bitmap.go c2-net-discovery uptime bitmap helpers, bit-identical to pkg/visor/stats/bitmap.go and pkg/deployment/tpd/store/redis_uptime.go's timeline format.

Each UTC day is encoded as 288 bits (one per 5-minute slot) in 36 raw bytes, MSB-first. Bit set = the recording process was alive during at least one keepalive within the slot. Duplicated from the visor's stats package to keep this package leaf-importable from services without pulling in pkg/visor/...; consolidate if a third caller needs the same helpers.

Package serviceuptime pkg/serviceuptime/recorder.go c2-net-discovery recorder that turns "this process is alive" into bbolt rows.

Lifecycle:

  1. New() opens the Store and writes the session-start row immediately, so a process that crashes during config parsing still leaves a trace.
  2. Start() kicks off the keepalive goroutine, which on every tick refreshes the session row's LastSeen and sets the current 5-min slot bit.
  3. Close() flushes the final keepalive write and shuts down.

Lifecycle is split so callers can write the session row before any subsystem init (cheap), then defer Start() until after the bind / listener is up (avoiding ticks that would mark slots while the service can't actually serve).

Package serviceuptime pkg/serviceuptime/render.go c2-net-discovery renderer for the CLI. Both `skywire cli visor uptime` and `skywire cli svc <name> uptime` go through here so the output is uniform across a deployment.

Package serviceuptime pkg/serviceuptime/store.go c2-net-discovery persistence for a service's own uptime / version history.

On-disk layout:

meta/                  — schema version, instance ID
sessions/              — keyed by start_unix_ts (8-byte big-endian);
                         value JSON SessionRecord
slots/                 — sub-buckets per UTC YYYY-MM-DD; value 36-byte bitmap

Source-of-truth for the running process. A Recorder wraps the Store and drives the keepalive loop that turns "process is alive" into rows + bits. Anything reading the data (HTTP handler, CLI) goes through the Store directly.

Index

Constants

View Source
const BitmapSize = SlotsPerDay / 8 // 36

BitmapSize is the byte length of a one-day bitmap.

View Source
const DefaultKeepaliveInterval = 60 * time.Second

DefaultKeepaliveInterval is how often the recorder refreshes LastSeen and sets the current slot bit. 60s gives one mark per minute, well below the 5-min slot resolution and inexpensive on bbolt (one Put per tick).

View Source
const DefaultPruneInterval = time.Hour

DefaultPruneInterval is how often the recorder runs Prune in the background. Once an hour is enough — retention is in days, missing a sweep by an hour doesn't change anything observable.

View Source
const DefaultRetention = 35 * 24 * time.Hour

DefaultRetention is the trailing window the recorder retains in bbolt. Matches the visor stats package's default so visor and services share retention semantics.

View Source
const SchemaVersion = 1

SchemaVersion is bumped on any breaking change to bucket layout.

View Source
const SlotsPerDay = 24 * 60 / 5 // 288

SlotsPerDay is the number of 5-minute slots in a UTC day.

Variables

View Source
var ErrSchemaMismatch = errors.New("serviceuptime: schema version mismatch")

ErrSchemaMismatch is returned by OpenStore when the persisted schema version doesn't match the binary's.

Functions

func CountSlots

func CountSlots(bm []byte) int

CountSlots returns how many bits are set across the bitmap.

func CurrentSessionHandler

func CurrentSessionHandler(recorder *Recorder) http.HandlerFunc

CurrentSessionHandler serves GET /uptime/now → JSON SessionRecord. Reads the in-flight session row directly from the recorder, so a caller can ask "what version is running right now" without a bbolt round-trip.

func DatesHandler

func DatesHandler(store *Store) http.HandlerFunc

DatesHandler serves GET /uptime/dates → JSON []string.

func FormatBitmap

func FormatBitmap(bm []byte) string

FormatBitmap renders a 36-byte bitmap as a 288-character ASCII line — '#' for set bits, '.' for unset — matching TPD's v3 timeline shape. For dashboards that want a one-liner per day.

func FormatHistory

func FormatHistory(sessions []SessionRecord, gapThreshold, restartLoopThreshold time.Duration) string

FormatHistory renders sessions as a chronological table. Gaps between adjacent sessions where the next StartedAt is more than gapThreshold after the previous LastSeen surface as "(down: <dur>)" rows so an operator can see when the service wasn't running.

Output shape:

2026-04-28 12:00:11Z  →  2026-04-28 12:00:14Z   3.0s   v1.3.46  (restart loop?)
2026-04-28 12:00:18Z  →  2026-04-28 12:00:21Z   3.0s   v1.3.46  (restart loop?)
2026-04-28 12:00:25Z  →  2026-04-29 04:31:07Z  16.5h   v1.3.46
                                  (down: 4m12s)
2026-04-29 04:35:19Z  →  2026-05-04 21:00:00Z   5.7d   v1.3.47

Sessions whose Duration is below restartLoopThreshold are tagged.

func GetSlot

func GetSlot(bm []byte, slot int) bool

GetSlot reports whether the bit for slot is set in bm.

func RegisterReadOnlyRoutes

func RegisterReadOnlyRoutes(r *httputil.Router, store *Store)

RegisterReadOnlyRoutes mounts the read endpoints minus /uptime/now, for callers with a Store but no live Recorder (e.g. an inspector tool reading a copied bbolt file).

func RegisterRoutes

func RegisterRoutes(r *httputil.Router, recorder *Recorder)

RegisterRoutes mounts all /uptime/* routes on r. TPD, AR, RF and UT call this from their api.New constructor.

func SessionsHandler

func SessionsHandler(store *Store) http.HandlerFunc

SessionsHandler serves GET /uptime/sessions → JSON []SessionRecord. Query: since=<RFC3339|unix>, limit=<n> (most-recent N rows).

func SetSlot

func SetSlot(bm []byte, slot int)

SetSlot sets the bit for the given slot index in bm. bm must be at least BitmapSize bytes; out-of-range slots are ignored so the helper is safe to call with computed indices at day boundaries.

func SlotForTime

func SlotForTime(t time.Time) int

SlotForTime returns the 0-based 5-minute slot index for t (in UTC).

func TimelineHandler

func TimelineHandler(store *Store) http.HandlerFunc

TimelineHandler serves GET /uptime/timeline → 36-byte octet-stream. Query: date=YYYY-MM-DD (defaults to today UTC).

Types

type Config

type Config struct {
	// Service is the identifier written into each session row's
	// Service field — "skywire-visor", "transport-discovery", etc.
	Service string
	// Version / Commit identify the running binary. Captured at New()
	// and stamped into the session row; never refreshed mid-session
	// (a binary's version doesn't change without a restart).
	Version string
	Commit  string
	// KeepaliveInterval, Retention, PruneInterval override the
	// defaults. Zero leaves the default in place.
	KeepaliveInterval time.Duration
	Retention         time.Duration
	PruneInterval     time.Duration
	// Now overrides the wall clock for tests. Production callers leave nil.
	Now func() time.Time
}

Config tunes the Recorder. Zero values get the Default* constants.

type Recorder

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

Recorder owns a running service's session row and slot bitmap.

func New

func New(path string, cfg Config) (*Recorder, error)

New opens the store and writes the initial session row. The recorder is ready to read but does not start the keepalive ticker until Start() is called.

path is the bbolt file location. If the file is missing it's created; if it exists with a different schema version the call errors out so the operator can decide whether to migrate.

func (*Recorder) Close

func (r *Recorder) Close() error

Close stops the keepalive goroutine, performs one final tick so LastSeen reflects shutdown time, and closes the underlying store. Safe to call from a signal handler.

func (*Recorder) CurrentSession

func (r *Recorder) CurrentSession() SessionRecord

CurrentSession returns a copy of the in-flight session row, useful for /uptime/now style endpoints that want to surface the running binary's version without a bbolt round-trip.

func (*Recorder) Start

func (r *Recorder) Start()

Start launches the keepalive + prune goroutines. Idempotent: a second Start is a no-op.

func (*Recorder) Store

func (r *Recorder) Store() *Store

Store returns the underlying read/write store. Callers (HTTP handlers, CLI rendering) use this to query history. Writes outside the keepalive loop are tolerated but not expected.

type SessionRecord

type SessionRecord struct {
	StartedAt  time.Time `json:"started_at"`
	LastSeen   time.Time `json:"last_seen"`
	Version    string    `json:"version,omitempty"`
	Commit     string    `json:"commit,omitempty"`
	PID        int       `json:"pid,omitempty"`
	InstanceID string    `json:"instance_id,omitempty"`
	// Service is the human-readable service name ("skywire-visor",
	// "transport-discovery", etc.) — purely informational, useful when
	// rendering history from a copied or shared bbolt file.
	Service string `json:"service,omitempty"`
}

SessionRecord is one process incarnation: the span between a binary starting and either shutting down cleanly or the next start. The recorder writes this row at startup with StartedAt and Version, then refreshes LastSeen on every keepalive tick. Gaps between adjacent sessions' LastSeen → next StartedAt are "service was not running" intervals; tightly-packed sessions with short LastSeen-StartedAt durations are restart-loop signal.

func (SessionRecord) Duration

func (r SessionRecord) Duration() time.Duration

Duration returns LastSeen − StartedAt, the observed up-time of this session. Returns 0 when the row is malformed (LastSeen before start).

type Store

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

Store is the bbolt-backed persistence layer. Safe for concurrent use. Paths and TTL are caller-supplied.

func OpenStore

func OpenStore(path string) (*Store, error)

OpenStore opens (or creates) the bbolt database at path and ensures the schema is at the expected version. instanceID is set on first open and never rewritten; it identifies the on-disk database independently of any single session.

func (*Store) Bitmap

func (s *Store) Bitmap(date time.Time) ([]byte, error)

Bitmap returns the persisted bitmap for the given UTC date, or a zero-filled bitmap if none exists. The result is a copy.

func (*Store) Close

func (s *Store) Close() error

Close releases the bbolt file handle.

func (*Store) Dates

func (s *Store) Dates() ([]string, error)

Dates lists every UTC date that has a stored bitmap, sorted ascending.

func (*Store) InstanceID

func (s *Store) InstanceID() string

InstanceID returns the durable per-database identifier set at first open. Useful for distinguishing two copies of the same service that share a deployment but differ in their on-disk state.

func (*Store) MarkSlot

func (s *Store) MarkSlot(date time.Time, slot int) error

MarkSlot sets the bit for slot in today's bitmap (UTC). The read-modify-write happens inside a single bbolt transaction; the OR is idempotent so concurrent ticks for the same slot race safely.

func (*Store) Prune

func (s *Store) Prune(cutoff time.Time) (sessions, bitmaps int, err error)

Prune drops rows older than cutoff: sessions whose LastSeen is strictly before cutoff, and slot bitmaps whose date is strictly before cutoff's UTC date. Returns sessions removed + bitmaps removed.

func (*Store) PutSession

func (s *Store) PutSession(rec SessionRecord) error

PutSession overwrites the row keyed by rec.StartedAt. Callers (the Recorder) read-modify-write under their own mutex; the store does not merge.

func (*Store) Sessions

func (s *Store) Sessions(since time.Time) ([]SessionRecord, error)

Sessions returns rows with StartedAt ≥ since, sorted ascending. Pass a zero time to read everything in the bucket.

Jump to

Keyboard shortcuts

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