apidiag

package
v2.18.0 Latest Latest
Warning

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

Go to latest
Published: Sep 25, 2026 License: GPL-3.0 Imports: 9 Imported by: 0

README

API timeout diagnostics

apidiag records content-free timing and pressure snapshots for HTTP JSON-RPC and dispatched WebSocket requests. It does not change request deadlines, responses, database admission, or cancellation behavior. Collection is disabled when telemetry is disabled, except for explicitly injected offline test sinks.

Lifecycle and timings

  • WebSocket request queue wait is recorded separately. The request budget still starts at execution, not enqueue time. Unbounded backup methods stay unbounded.
  • A deadline callback reports even when a handler or database admission lock is still blocked. Stage completion, error handling, and response cleanup share an exactly-once recorder.
  • A returned nested deadline or network timeout is labeled operation; expiry of the request context is labeled request. Disconnect/shutdown cancellation and ordinary successful completion produce no diagnostic event.
  • The recorder remains attached through response queueing, serialization, writing, and AfterWrite. HTTP request-body reading, pre-dispatch WebSocket failures, heartbeat frames, REST routes, and SSE are outside this envelope.
  • Timings are inclusive and may overlap. Do not add stage totals to estimate elapsed time. active_stages includes unfinished work; timeout_stage is the most recently started active stage, not proof of a particular root cause.
  • Shared hooks cover handler execution, WebSocket database admission and image slots, response building, response queueing, response writing, and callbacks. Search/browse also measure concurrency admission and database work. Browse's explicit connection acquisition has a separate database_pool phase. Other methods initially have shared envelope timings, not exhaustive internal spans.
  • WebSocket response_write includes encryption/session-lock work and Melody frame enqueueing. It does not establish socket delivery or client receipt.

Begin returns an idempotent completion function. Concurrent spans are bounded at 16. No request payload or arbitrary label can be attached to a span. When adding a method, update the explicit method-name allowlist only after privacy review; unknown names become unknown. A registration coverage test guards drift.

Pressure snapshots

The start and timeout snapshots use only SQL pool counters, atomics, and nonblocking reads of in-memory activity flags. No SQL query, filesystem probe, network call, profile, or runtime.ReadMemStats is performed to build them.

Both database pools expose open/in-use/idle/maximum counts. Pool wait deltas are shared process counters, not that request's own waits; decreasing counters make the delta unavailable. MediaDB additionally exposes indexing pool-boost, optimization, recovery, and transaction state. Scraping, playback, and service recovery are flags only. Contended state locks produce unknown, not false.

Runtime heap size, goroutine count, and process age use coarse power-of-two buckets. Durations and counters have upper bounds. Snapshot values are taken at slightly different instants and are clues, not an atomic view of the process.

Sentry boundary

telemetry.CaptureAPITimeout uses a fresh scope. The SDK's BeforeSend hook reconstructs the entire event from a typed report carried in an EventHint, retaining only fixed fields, validated enums, bounded numbers, and SDK/build metadata. The message is always API request timed out.

No request body, parameters, SQL, query, path, filename, URL, header, token, client/request ID, input hash, raw error, stack, breadcrumb, attachment, or inherited context/user is included. These events do not carry the existing installation user ID, so Sentry affected-user counts are not available for them. Offline fake-transport tests inject private canaries into error text and scope fields and check the serialized event.

Each recorder emits at most one structured timeout event. Central API error handling avoids a second unstructured timeout/cancellation event; known duplicate history/activity query logs follow the same rule. Other legacy/background telemetry keeps its existing policy: this boundary is not a global scrubber or a full tracing/deduplication system.

Request shape, search terms, SQL tracing, library sizes, device profiling, and performance fixes are deliberately excluded. These diagnostics identify the next place to investigate; they do not by themselves establish why a request is slow.

Documentation

Overview

Package apidiag records bounded, content-free API timeout diagnostics.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Begin

func Begin(ctx context.Context, stage Stage) func()

Begin returns an idempotent stage completion function. Excess concurrent stages are ignored rather than allowing unbounded diagnostic state.

func IsContextFailure

func IsContextFailure(ctx context.Context, err error) bool

IsContextFailure identifies errors that belong in request diagnostics, not a second unstructured error event. Cancellation remains distinct from timeout.

func IsTimeout

func IsTimeout(err error) bool

func MethodName

func MethodName(method string) string

func RecordError

func RecordError(ctx context.Context, err error)

RecordError reports nested operation deadlines as well as request deadlines. It never copies the error text, which may contain private request contents.

Types

type DatabaseProvider

type DatabaseProvider interface {
	APIDiagnostics() DatabaseSnapshot
}

DatabaseProvider is optional; existing database interfaces remain unchanged. Implementations must not query SQL, inspect files, or wait for application locks.

type DatabaseSnapshot

type DatabaseSnapshot struct {
	Pool        Pool
	Transaction State
	Indexing    State
	Optimizing  State
	Recovery    State
}

type DeadlineKind

type DeadlineKind uint8
const (
	RequestDeadline DeadlineKind = iota
	OperationDeadline
)

func (DeadlineKind) String

func (k DeadlineKind) String() string

type Pool

type Pool struct {
	WaitDuration time.Duration
	WaitCount    int64
	Max          int
	Open         int
	InUse        int
	Idle         int
	Available    bool
}

Pool contains process-wide counters, not attribution to an individual request.

func PoolSnapshot

func PoolSnapshot(db *sql.DB) Pool

PoolSnapshot reads in-memory database/sql counters, never a connection/query.

type Recorder

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

Recorder is scoped to one request. Timings of nested stages are inclusive, so their sum is not the request's elapsed time.

func FromContext

func FromContext(ctx context.Context) *Recorder

func New

func New(ctx context.Context, method string, transport Transport, queueWait time.Duration,
	snapshot func() Snapshot, emit func(Report), clock clockwork.Clock,
) (context.Context, *Recorder)

New starts diagnostics without changing the context's deadline or cancellation. Snapshot and emit run outside the recorder lock and must be safe concurrently.

func (*Recorder) Finish

func (r *Recorder) Finish()

Finish detaches the deadline callback after the complete response lifecycle. A deadline racing completion is reported once, never lost or duplicated.

func (*Recorder) Report

func (r *Recorder) Report(kind DeadlineKind)

type Report

type Report struct {
	Started       time.Time
	Method        string
	StartSnapshot Snapshot
	EndSnapshot   Snapshot
	Durations     [stageCount]time.Duration
	Elapsed       time.Duration
	Budget        time.Duration
	QueueWait     time.Duration
	ActiveStages  [stageCount]bool
	Stage         Stage
	Transport     Transport
	Kind          DeadlineKind
}

type Snapshot

type Snapshot struct {
	MediaDB      DatabaseSnapshot
	UserDB       DatabaseSnapshot
	Scraping     State
	MediaPlaying State
	Recovery     State
}

type Stage

type Stage uint8

Stage identifies code-owned phases, never request-supplied operation names.

const (
	Dispatch Stage = iota
	DatabaseLock
	DatabasePool
	ConcurrencySlot
	Handler
	Database
	ResponseBuild
	ResponseQueue
	ResponseWrite
	AfterWrite
)

func (Stage) String

func (s Stage) String() string

type State

type State uint8

State distinguishes an unavailable snapshot from an observed false value.

const (
	Unknown State = iota
	Inactive
	Active
)

func Observed

func Observed(active bool) State

func (State) String

func (s State) String() string

type Transport

type Transport uint8
const (
	HTTP Transport = iota
	WebSocket
)

func (Transport) String

func (t Transport) String() string

Jump to

Keyboard shortcuts

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