Documentation
¶
Overview ¶
Package fleet is Cockpit's fleet read model: one versioned document of this machine's repositories, worktrees, branches, pull requests and agents, and of every other machine's published snapshot, kept current by a background snapshotter and served without a request ever running Git (cockpit#req:fleet-read-model, cockpit#req:no-fleet-scan-on-the-request-path).
The types in this file are the security boundary of the read model. They hold exactly the metadata field set of cockpit#req:anonymous-local-reads- metadata-only and nothing else, and every source value reaches them through the mapping in mapping.go field by field: no source struct is copied, so a field a source gains later cannot leak. A path, a file name, a diff, a commit subject, a task summary, a prompt or a log body has no field here to land in.
Index ¶
- Constants
- Variables
- func Fingerprint(checkout string) (string, error)
- func NewEnvelope(document Document, metrics MetricsResponse, now time.Time, metricsOnly bool) (Envelope, ExportDrops)
- func Register(server *cockpit.Server, snapshotter *Snapshotter)
- func Unlistable(document Document) bool
- type ActivityCollector
- type Agent
- type BadEnvelopeError
- type Branch
- type BranchCollector
- type BranchRef
- type BranchesResponse
- type CodeGrapherProvider
- type CodeIndex
- type CodeIndexCollector
- type CodeIndexPass
- type CodeIndexProvider
- type CodeStatistics
- type Collectors
- type Document
- type Entry
- type Envelope
- type EnvelopeMetrics
- type ExportDrops
- type ExportError
- type GitGate
- type HTTPExporter
- type HTTPRoute
- type Hardware
- type HerdrActivity
- type HerdrLister
- type IdentityCollector
- type KindCount
- type LinkedWorktree
- type LocalCodeIndex
- type LocalCollectors
- func (c LocalCollectors) Branches(ctx context.Context, repository discover.Repo) ([]BranchRef, error)
- func (c LocalCollectors) Collectors(remote RemoteCollector) Collectors
- func (c LocalCollectors) DefaultBranch(ctx context.Context, repository discover.Repo) string
- func (c LocalCollectors) GitUsable(ctx context.Context) (bool, error)
- func (c LocalCollectors) Identity(ctx context.Context, repository discover.Repo) (string, error)
- func (c LocalCollectors) PullRequests(ctx context.Context) ([]worktrees.RegisteredPullRequestBinding, error)
- func (c LocalCollectors) Readme(ctx context.Context, repository discover.Repo, branch string) ([]byte, error)
- func (c LocalCollectors) Record(worktree string) (WorktreeRecord, bool)
- func (c LocalCollectors) Repositories(ctx context.Context) ([]discover.Repo, error)
- func (c LocalCollectors) Runs(context.Context) ([]agents.Result, error)
- func (c LocalCollectors) Sessions(context.Context) ([]session.View, error)
- func (c LocalCollectors) Worktrees(ctx context.Context, repository discover.Repo) ([]LinkedWorktree, error)
- type LocalStateCollector
- type LocalStateReader
- type LocalTerminals
- type Machine
- type MetricsAnswer
- type MetricsResponse
- type MetricsSource
- type Options
- type ProviderStatistics
- type PullRequest
- type PullRequestCollector
- type PullRequestObserver
- type ReadError
- type ReadmeCollector
- type RecordCollector
- type RemoteCollector
- type RemoteError
- type RemoteExporter
- type RemotePublisher
- type RemoteTarget
- type RemoteTransport
- type Repository
- type RepositoryCollector
- type RunCollector
- type SSHExporter
- type SSHRoute
- type SessionCollector
- type Snapshotter
- func (s *Snapshotter) Branches(id string) (payload cockpit.Payload, found bool)
- func (s *Snapshotter) ChangeToken() string
- func (s *Snapshotter) CheckedAt() time.Time
- func (s *Snapshotter) Export(metricsOnly bool) Envelope
- func (s *Snapshotter) ExportPayload(metricsOnly bool) (payload cockpit.Payload, failure string)
- func (s *Snapshotter) MachineMetrics(id string) (payload cockpit.Payload, found bool)
- func (s *Snapshotter) MachineRoutes() []cockpit.MachineRoute
- func (s *Snapshotter) Payload() cockpit.Payload
- func (s *Snapshotter) PublishExtras() remotestate.Extras
- func (s *Snapshotter) Refresh(ctx context.Context) error
- func (s *Snapshotter) RefreshRepository(ctx context.Context, id string) error
- func (s *Snapshotter) Start(ctx context.Context) (stop func())
- type TerminalFile
- type TerminalRecords
- type Throughput
- type ThroughputDay
- type ThroughputTask
- type Worktree
- type WorktreeCollector
- type WorktreeRecord
Constants ¶
const ( ErrorProviderTimeout = "provider_timeout" ErrorProviderFailed = "provider_failed" ErrorProviderOutput = "provider_output_invalid" )
The short codes a statistics Error can hold. They name the kind of failure and never carry a path or the text a command printed.
const ( RouteLocal = "local" RouteCached = "cached" )
The routes an entry can have (cockpit#req:route-and-freshness-are- explicit): local for state this daemon observed itself, cached for state read from another machine's published snapshot, and live-remote (RouteLiveRemote) for state read in the background from another machine's own export (cockpit-views#req:remote-entries-replace-cached).
const ( AgentSession = "session" AgentRun = "run" )
The agent kinds: a registered session or a dispatched run.
const ( BranchLocal = "local" BranchRemote = "remote" )
The two branch scopes.
const ( OwnerActive = "active" OwnerIdle = "idle" OwnerOrphaned = "orphaned" OwnerUnknown = "unknown" )
The owner states (cockpit-views#req:owner-state-vocabulary), from the liveness of the worktree's recorded owner process: a worktree mapped on this machine is active or orphaned when that process is alive or gone and unknown when none is recorded. A worktree from another machine's snapshot keeps the value that machine published when it is one of these four and has none otherwise.
const ( ErrorTimeout = "timeout" ErrorReadFailed = "read_failed" )
The short codes a repository's Error can hold. They name the kind of failure and never carry a path or the text a command printed.
const ( CodeIndexFresh = "fresh" CodeIndexStale = "stale" CodeIndexDiverged = "diverged" CodeIndexPending = "pending" CodeIndexFailed = "failed" CodeIndexNever = "never" )
The six states a code index can be in, as code-index-freshness defines them.
const ( ActivityWorking = "working" ActivityBlocked = "blocked" ActivityIdle = "idle" ActivityDone = "done" ActivityUnknown = "unknown" )
The activity values of an agent entry: herdr's five agent statuses, which are the only thing the cockpit reads from herdr (never the screen).
const ( PublishErrorCollectFailed = "collect_failed" PublishErrorStore = "store_unavailable" PublishErrorFailed = "publish_failed" PublishErrorOptional = "optional_fields_dropped" )
The codes of Machine.publish_error: the diagnostics of the periodic remote publisher (internal/remotestate/periodic), a closed list, and never the text of an error. A code outside it is dropped.
const ( ErrorDaemonNotRunning = "daemon_not_running" ErrorExportRefused = "export_refused" ErrorExportFailed = "export_failed" )
Three of the error codes of an export that could not be made, printed as an ExportError: no daemon is running, the daemon refuses anonymous reads, or anything else went wrong (an unreadable record, a daemon that answers badly or that this binary does not understand).
const ( ScopeOwn = "own" ScopeMachine = "machine" )
The scopes of the fleet route's "scope" query parameter, which the export verb reads this machine with (cockpit-views#req:cockpit-export-verb): own is the document with this machine's own entries only, exactly the fleet of its export, and machine the same with no entry but this machine's own machine entry, which is all a metrics-only export needs of it. Neither is a read of the other machines, so neither is demand for them. Any other value, and none, is the whole document.
const ( FleetRoute = "fleet" BranchesRoute = "branches" ReadmePath = cockpit.APIPrefix + "readme" )
Routes the read model serves under cockpit.APIPrefix. The README route has no path parameter, because Cockpit's owner routes are exact paths: the repository's stable id travels in the "repository" query parameter.
const ( RouteLiveRemote = "live-remote" RouteNone = "none" ReasonNoSource = "no_source" ReasonUnsupported = "unsupported" // ReasonStale is the reason of a machine whose only data is a published // sample older than MaxCachedSampleAge. ReasonStale = "stale" )
The sources of a metrics answer, and the reasons of an answer with none.
const ( // DefaultPullRequestLimit is the most pull requests observed per pass when // cockpit.pull_request_limit is not set. DefaultPullRequestLimit = 10 // DefaultPullRequestHourlyBudget is the most observations in any rolling // hour when cockpit.pull_request_hourly_budget is not set. An observation // is prsnapshot.ReadsPerObservation (6) GitHub reads for an open pull // request, whatever its checks say, and prsnapshot.ReadsPerInactiveObservation // (1) for a merged or closed one, so the default is at most 720 reads an // hour and the ceiling of 400 at most 2,400; a read answered 304 by the // ETag cache is not charged to the rate limit. DefaultPullRequestHourlyBudget = 120 )
const ( TransportHTTP = "http" TransportSSH = "ssh" )
The transports a machine's export can be read over.
const ( RemoteErrorHTTPAuthFailed = "http_auth_failed" RemoteErrorAuthFailed = "auth_failed" RemoteErrorTimeout = "timeout" RemoteErrorWBMissing = "wb_missing" RemoteErrorWBTooOld = "wb_too_old" RemoteErrorDaemonNotRunning = "daemon_not_running" RemoteErrorExportRefused = "export_refused" RemoteErrorBadPayload = "bad_payload" // RemoteErrorWarmingUp says the remote daemon keeps answering that its first // pass has not ended and nothing fresh is held of it. RemoteErrorWarmingUp = "remote_warming_up" // RemoteErrorExportTooLarge says the machine's entries, live or published, // were left out because the fleet document would be over its size bound with // them. RemoteErrorExportTooLarge = "export_too_large" // RemoteErrorSelfExport says the export read from the configured address is // this machine's own. RemoteErrorSelfExport = "self_export" // RemoteErrorClockSkew says the export carries a time further ahead of this // daemon's clock than two machines' clocks may ordinarily differ (maxSkew): // one of the two clocks is wrong. It is not bad_payload, which would send the // owner looking for a fault in the export. RemoteErrorClockSkew = "clock_skew" )
The codes a machine entry's remote_error can hold (cockpit-views#req:remote-error-is-visible). The http_* codes name the HTTP transport and the others the SSH transport, except bad_payload and clock_skew, which either can produce.
const ( // ThroughputWindowDays is the window of the block: today and the 29 UTC days // before it. ThroughputWindowDays = 30 // DefaultThroughputInterval is how long the collector goes between scans of // the terminal records when nothing asks for one sooner. DefaultThroughputInterval = 10 * time.Minute // DefaultTerminalReadLimit is the most records read in one scan; it is a // safety bound, because a scan reads the newest records first and stops at // the window. A scan that hits it reads the rest on the next refresh. DefaultTerminalReadLimit = 5000 // DefaultTerminalTotalLimit is the most terminal records the collector knows // of at once. DefaultTerminalTotalLimit = 20000 )
The throughput window and the bounds of the collector.
const DefaultCodeGrapherIndexer = "codegrapher"
DefaultCodeGrapherIndexer is the executor name CodeGrapher is followed under unless cockpit.code_index_indexer says otherwise.
const DefaultInterval = time.Minute
DefaultInterval is the refresh interval when cockpit.refresh_interval is not set (cockpit#req:snapshot-refresh). It was chosen from a one-off measurement of the production local collectors on the founder's projects root on 2026-10-01 (the measuring test was removed: it read a real directory named by the environment, which no test may do): for 417 repositories, 490 worktrees and 3824 branches the first partial document took 64 ms, the whole first pass 3.1 s and a pass in which no fingerprint moved 0.45 s. A full pass is about 5% of a minute, inside the 10% budget, and the minute is the floor an interval is allowed to have.
const ErrorGitTooOld = "git_too_old"
ErrorGitTooOld is the document's error code when Git is older than the oldest version that is safe to run against a repository.
const ErrorRepositoriesUnreadable = "repositories_unreadable"
ErrorRepositoriesUnreadable is the document's own error code when the repositories could not be listed.
const ErrorWarmingUp = "warming_up"
ErrorWarmingUp is the fourth: the daemon's first pass has not ended, so its fleet is partial and is not exported (a reader keeps what it holds).
const ExportDropsHeader = "X-Wb-Cockpit-Export-Dropped"
ExportDropsHeader is the response header of a scoped read that says how many of this machine's entries the daemon left out of it, by kind: four numbers, repositories, worktrees, pull requests and agents. It holds numbers only.
const ExportReaderHeader = "X-Wb-Cockpit-Export"
ExportReaderHeader is the request header by which the export verb says its read of the fleet document is another machine's daemon reading this one, not a person looking: such a read is not demand. Without it two machines that read each other would each keep the other's fleet in demand for ever.
const ExportSchemaVersion = 1
ExportSchemaVersion is the envelope format this binary writes and reads.
const MachineExportPath = "/v0/workbench/machines/export"
MachineExportPath is the path of the hub route that serves a machine's own export (cockpit-views#req:hub-export-route); hub.MachineExportPath is the same path, and a test holds the two together.
const MaxCachedSampleAge = 24 * time.Hour
MaxCachedSampleAge is the age after which a published sample is expired.
const MaxEnvelopeBytes = 8 << 20
MaxEnvelopeBytes bounds an envelope as printed and as read.
const MaxReadmeBytes = 1 << 20
MaxReadmeBytes caps the README the owner route serves: one mebibyte, far beyond any README worth rendering and small enough to hold in memory per request.
const (
MetricsRoute = "machine-metrics"
)
MetricsRoute is the machine-metrics route under cockpit.APIPrefix, and metricsQuery its machine parameter: the machine's id, not its name.
const ReasonCachedRepository = "cached_repository"
ReasonCachedRepository is the reason of an empty branch list for a repository cached from another machine, whose snapshot carries no branches.
const SchemaVersion = 2
SchemaVersion is the document format this binary writes: 2 since cockpit-views#req:schema-version-2, which took the branches out of the document and added the fields below.
Variables ¶
var ErrNoRoute = errors.New("the machine has no route for this transport")
ErrNoRoute is what an exporter returns for a target the local configuration gives it no route to. It is not a failure: the next transport is asked.
var ErrRemoteWarmingUp = errors.New("the remote daemon is warming up")
ErrRemoteWarmingUp is what an exporter returns when the remote daemon says its first pass has not ended. It is not a failure and not a reason to try another transport: the remote is healthy and has nothing complete to give yet, so what is held of it is kept.
var ErrUnknownRepository = errors.New("unknown repository")
ErrUnknownRepository is returned by RefreshRepository for an id the last scan did not produce.
Functions ¶
func Fingerprint ¶
Fingerprint is a cheap signal that a canonical clone's Git state has not changed: a hash over file metadata, never over Git output, so computing it runs no Git command and reads no file content. The signals are
- the size and modification time of .git/HEAD, .git/index, .git/packed-refs, .git/config and .git/reftable/tables.list (the checked-out branch, the staged and tracked state, every packed ref, the remotes and default branch the configuration names, and the reftable backend's table list), and the names in .git/reftable;
- every file under .git/refs but refs/wb, by relative name, size and modification time (a loose branch, remote-tracking ref or tag created, moved or deleted). refs/wb is WB's own private namespace, which reading a repository's branches writes to; counting it would make every read change the fingerprint it was keyed on;
- the worktree list: each entry of .git/worktrees with the size and modification time of its HEAD and index, and the directory names under <clone>/.worktrees (a worktree created, removed, switched or staged).
Edits that touch no index and no ref do not move it, and neither does WB state that lives outside Git (a heartbeat, a manifest); the snapshotter reads that state on every pass instead, and uses the fingerprint only to skip the Git work.
func NewEnvelope ¶ added in v0.175.0
func NewEnvelope(document Document, metrics MetricsResponse, now time.Time, metricsOnly bool) (Envelope, ExportDrops)
NewEnvelope is the one function that builds an envelope, for the CLI verb (from what the daemon's routes served) and for the hub route (from the daemon's own state in process). It keeps this machine's own entries only: an entry that is cached from another machine, or live from one, is never re-exported. A metrics-only envelope has no fleet.
An entry of this machine that the strict decoder would refuse is left out and counted (in the envelope's Dropped, and by kind in the drops returned), so one odd entry cannot take a machine's whole export down: an entry with a field that breaks its rule (a name over the cap, a time in the future), one that breaks a rule between entries (an id used twice, a web address that is not the one built from its host and name, a pull request address off its repository's host), and every worktree, pull request and agent of a repository that was left out. The machine entry itself is always kept: an export whose machine entry is not valid is refused as a whole.
Every scalar the document carries besides its collections is about this machine alone: repositories_total, repositories_scanned and diagnostics count what this daemon scanned and read locally (another machine's entries are mapped separately and do not enter them), error and code_index_provider are this daemon's own, snapshot_at is its own snapshot's time, and agents_truncated says that this machine's own agents were cut at 200, so it stays true to the envelope's own agents list.
func Register ¶
func Register(server *cockpit.Server, snapshotter *Snapshotter)
Register adds the fleet, branches and machine-metrics metadata routes and the owner-only README route to server, and gives it the machines' SSH routes for an owner's session response. Call it before the server's mounts are taken.
func Unlistable ¶ added in v0.175.0
Unlistable reports whether document is one whose repositories could not be listed and that holds none of them: this machine cannot say what it has, so its fleet is not exported (an empty fleet would replace what a reader holds of it). The export is then ErrorExportFailed, which a reader shows as an error of that machine; the metrics-only export is made.
Types ¶
type ActivityCollector ¶ added in v0.175.0
ActivityCollector reads the activity herdr reports for the agents it hosts: herdr's status (working, blocked, idle, done or unknown) by harness session id, and nothing else. A machine with no herdr, or one that does not answer in time, returns an error, which leaves every agent with no activity.
type Agent ¶
type Agent struct {
Entry
Kind string `json:"kind"`
SessionID string `json:"session_id,omitempty"`
RunID string `json:"run_id,omitempty"`
Runtime string `json:"runtime,omitempty"`
Model string `json:"model,omitempty"`
State string `json:"state"`
Activity string `json:"activity,omitempty"`
Repository string `json:"repository,omitempty"`
Task string `json:"task,omitempty"`
Worktrees []string `json:"worktrees,omitempty"`
StartedAt time.Time `json:"started_at,omitzero"`
FinishedAt time.Time `json:"finished_at,omitzero"`
ExitCode *int `json:"exit_code,omitempty"`
}
Agent is a registered session or a dispatched run: identifiers, runtime, model and state, the repository, task and worktrees it works in, when it started and, for a finished run, when it finished and its exit code. Activity is herdr's status of a session of this machine, absent when herdr does not report it. A session has Repository, Task and Worktrees only when a worktree's declared owner process is the session's; they are never guessed.
type BadEnvelopeError ¶ added in v0.175.0
BadEnvelopeError is a refused envelope. Reason names the rule and the field's path, from fixed text and the document's own field names: it never holds the remote's text, so it is safe to log and never to be shown as the remote's words.
ClockSkew says the envelope was refused for a time ahead of this daemon's clock by more than maxSkew: the two machines' clocks disagree, which is not a fault of the payload and is shown under its own code.
func (*BadEnvelopeError) Error ¶ added in v0.175.0
func (e *BadEnvelopeError) Error() string
type Branch ¶
type Branch struct {
Entry
Repository string `json:"repository"`
Name string `json:"name"`
Scope string `json:"scope"`
Task string `json:"task,omitempty"`
Worktree string `json:"worktree,omitempty"`
Upstream string `json:"upstream,omitempty"`
Ahead int `json:"ahead,omitempty"`
Behind int `json:"behind,omitempty"`
UpstreamGone bool `json:"upstream_gone,omitempty"`
LastActivityAt time.Time `json:"last_activity_at,omitzero"`
}
Branch is one local or remote branch, served by the branches route and not part of the fleet document. Worktree is the id of the worktree that has it checked out, and Task that worktree's task, when there is one. Upstream, Ahead, Behind and UpstreamGone describe a local branch's tracking state.
type BranchCollector ¶
type BranchCollector interface {
Branches(ctx context.Context, repository discover.Repo) ([]BranchRef, error)
DefaultBranch(ctx context.Context, repository discover.Repo) string
}
BranchCollector lists one repository's local and remote branches and names its default branch (empty when it has none). Like WorktreeCollector it is asked only when the fingerprint moved.
type BranchRef ¶
type BranchRef struct {
Name string
Scope string
Upstream string
Ahead int
Behind int
UpstreamGone bool
CommittedAt time.Time
}
BranchRef is one local or remote branch ref.
type BranchesResponse ¶ added in v0.175.0
type BranchesResponse struct {
Repository string `json:"repository"`
Branches []Branch `json:"branches"`
Reason string `json:"reason,omitempty"`
}
BranchesResponse is what the branches route answers for one repository: its branches, and for a repository whose branches this machine does not hold (an entry cached from another machine) an empty list and a Reason.
type CodeGrapherProvider ¶
type CodeGrapherProvider struct {
// Binary is the command; empty means "codegrapher", looked up on PATH.
Binary string
// IndexerName is the hooks executor whose receipts this follows; empty
// means "codegrapher".
IndexerName string
// Runner runs the command; nil means the real runner.
Runner runner.Runner
}
CodeGrapherProvider reads CodeGrapher's statistics with its own command: `codegrapher status --json --path <checkout>`, which prints one JSON object (initialized, projectPath, fileCount, nodeCount, edgeCount, nodesByKind). WB never opens CodeGrapher's database. The command runs in its own process group with a sanitised environment and capped output, and nothing it prints to standard error is kept.
Symbols are CodeGrapher's nodes other than its file nodes (files are counted on their own) and kinds its node kinds. CodeGrapher resolves a path to the nearest initialised ancestor; an answer for any directory but the checkout asked about is treated as "not indexed", so a checkout never borrows another's statistics.
func (CodeGrapherProvider) Indexer ¶
func (p CodeGrapherProvider) Indexer() string
Indexer is the executor name whose receipts this provider follows.
func (CodeGrapherProvider) Name ¶
func (CodeGrapherProvider) Name() string
Name is the provider's name.
func (CodeGrapherProvider) Statistics ¶
func (p CodeGrapherProvider) Statistics(ctx context.Context, checkout string) (ProviderStatistics, error)
Statistics runs the command for checkout.
type CodeIndex ¶
type CodeIndex struct {
Indexer string `json:"indexer"`
State string `json:"state"`
Behind int `json:"behind,omitempty"`
ReceiptAt time.Time `json:"receipt_at,omitzero"`
Statistics *CodeStatistics `json:"statistics,omitempty"`
// contains filtered or unexported fields
}
CodeIndex is the freshness of one indexer's index of a checkout (cockpit#req:code-index-freshness-is-shown): the indexer's configured name, its state, for a stale index the number of commits HEAD is ahead of the receipt (Behind), and the time of the receipt the state was read from, when there is one. Nothing else of a receipt, and no path, reaches the document.
Statistics is what the configured code-index provider reported for the checkout when the receipt was written (cockpit#req:code-index-summary): it is present only on the indexer the provider follows, and absent when the provider has not been asked. Statistics appear only for a checkout the configured indexer has a receipt for: the provider's command opens the index read-write and may run Git, which the snapshotter's read-only rule forbids for a checkout WB's hook never indexed (it may hold an index a hostile repository committed).
type CodeIndexCollector ¶
type CodeIndexCollector interface {
Begin() (CodeIndexPass, error)
}
CodeIndexCollector reads the code-index freshness of checkouts from the indexer receipts (cockpit#req:code-index-freshness-is-shown). Begin reads the receipts and the queue once, for a pass, and the pass it returns answers for any number of repositories. A pass from which the receipts could not be read is an error, and then no entry carries a code index: an unreadable receipt stream is not evidence that nothing was indexed.
type CodeIndexPass ¶
type CodeIndexPass interface {
Key(identity string, checkouts []string) string
States(ctx context.Context, identity string, checkouts []string) (states map[string][]CodeIndex, complete bool)
}
CodeIndexPass answers for the repositories of one snapshot pass. Key is a cheap digest, which runs no Git command, of everything a state depends on but HEAD (the configured indexers, the receipts and the queue for these checkouts), so a caller can skip States while neither it nor Git state moved. States maps each checkout, as it was passed in, to one CodeIndex per indexer configured for the repository. An indexer whose state could not be told is left out, never guessed, and complete is then false, so the caller asks again next pass instead of keeping the gap.
type CodeIndexProvider ¶
type CodeIndexProvider interface {
Name() string
Indexer() string
Statistics(ctx context.Context, checkout string) (ProviderStatistics, error)
}
CodeIndexProvider reports code-index statistics for a checkout. Name is the provider's name, which the document carries; Indexer is the configured name of the indexer whose receipts the provider follows, so its statistics are attached to that indexer's code-index entry; Statistics reads one checkout. A provider that runs a process must bound it (the snapshotter hands it a context with a timeout) and cap its output.
type CodeStatistics ¶
type CodeStatistics struct {
Indexed bool `json:"indexed"`
Files int `json:"files"`
Symbols int `json:"symbols"`
Edges int `json:"edges"`
Kinds []KindCount `json:"kinds"`
Error string `json:"error,omitempty"`
}
CodeStatistics is a code index's statistics, counts only (cockpit#req:code-index-summary): whether the checkout has an index, and when it does the number of files, symbols and edges and the symbols by kind. These are statistics of the index, not counts of Cockpit entities. Error is a short code when the provider could not answer, and then the counts are zero; it is never the text a command printed or a path. Kinds is a list, never null.
type Collectors ¶
type Collectors struct {
Git GitGate
Repositories RepositoryCollector
Worktrees WorktreeCollector
Branches BranchCollector
Readme ReadmeCollector
Identity IdentityCollector
Records RecordCollector
PullRequests PullRequestCollector
Sessions SessionCollector
Runs RunCollector
Remote RemoteCollector
// Activity reads herdr's agent statuses; nil means no herdr, and no agent
// carries an activity.
Activity ActivityCollector
// CodeIndex reads indexer receipts; nil means no code-index freshness.
CodeIndex CodeIndexCollector
// CodeIndexProvider reports the statistics of a checkout's index; nil means
// no provider is configured, which the document reports.
CodeIndexProvider CodeIndexProvider
}
Collectors is every source the snapshotter reads. Remote may be nil: a machine with no remote provider configured has no other machines.
type Document ¶
type Document struct {
SchemaVersion int `json:"schema_version"`
SnapshotAt time.Time `json:"snapshot_at,omitzero"`
WarmingUp bool `json:"warming_up"`
RepositoriesTotal int `json:"repositories_total"`
RepositoriesScanned int `json:"repositories_scanned"`
Diagnostics int `json:"diagnostics"`
Error string `json:"error,omitempty"`
// CodeIndexProvider is the name of the configured code-index provider, or
// absent when none is configured, which is a normal state.
CodeIndexProvider string `json:"code_index_provider,omitempty"`
// RefreshIntervalSeconds is the daemon's snapshot refresh interval, which
// the application's freshness chip is measured against.
RefreshIntervalSeconds int `json:"refresh_interval_seconds"`
// The branches are not a collection of the document: each repository
// carries its counts and BranchesResponse serves the list per repository.
Machines []Machine `json:"machines"`
Repositories []Repository `json:"repositories"`
Worktrees []Worktree `json:"worktrees"`
PullRequests []PullRequest `json:"pull_requests"`
Agents []Agent `json:"agents"`
AgentsTruncated bool `json:"agents_truncated,omitempty"`
// PullRequestsThrottled says the hourly budget of pull request
// observations ran out with pull requests due, so their state is older than
// the cadence promises; each entry's checked_at says how old.
PullRequestsThrottled bool `json:"pull_requests_throttled,omitempty"`
// Throughput is this machine's landed-task throughput
// (cockpit-views#req:throughput-block), absent while no landed terminal
// record has both timestamps. It is local only and never exported.
Throughput *Throughput `json:"throughput,omitempty"`
}
Document is the fleet read model. It is published incrementally: while the first pass runs it holds the repositories scanned so far and WarmingUp stays true, and RepositoriesScanned of RepositoriesTotal says how far the pass is. Before the first repository completes it is empty and well formed. Diagnostics counts the pull requests whose repository could not be told. Error is a short code when the repositories could not be listed; the agents and other machines are still read. AgentsTruncated says the agents were capped.
type Entry ¶
type Entry struct {
ID string `json:"id"`
Machine string `json:"machine"`
MachineID string `json:"machine_id"`
Route string `json:"route"`
ObservedAt time.Time `json:"observed_at,omitzero"`
}
Entry is what every collection's entry carries: a stable identifier unique within its collection, the machine it belongs to, its route and when it was observed. Machine is the machine's name, which two logins can share; MachineID is the id of the machine entry it belongs to, which is unique, and is what a client filters by. For a cached entry ObservedAt is the publish time of the snapshot it came from.
type Envelope ¶ added in v0.175.0
type Envelope struct {
SchemaVersion int `json:"schema_version"`
// Machine is the exporting machine's name. It is informational: no reader
// places an entry by it.
Machine string `json:"machine"`
ExportedAt time.Time `json:"exported_at"`
Fleet *Document `json:"fleet,omitempty"`
Metrics *EnvelopeMetrics `json:"metrics,omitempty"`
// Dropped is the number of this machine's own entries left out because a
// value of theirs would not pass the envelope's rules (a name longer than
// the cap, a time in the future): one odd entry is dropped, and the export
// is still made.
Dropped int `json:"dropped,omitempty"`
}
Envelope is the export envelope. Fleet is absent in a metrics-only export.
func DecodeEnvelope ¶ added in v0.175.0
DecodeEnvelope reads an envelope strictly from reader: at most MaxEnvelopeBytes, a shape within its caps, one JSON value, no unknown field, then Validate. It is the only way a remote machine's envelope enters the daemon. metricsOnly says which of the two shapes was asked for.
func (Envelope) Validate ¶ added in v0.175.0
Validate refuses an envelope that breaks a rule of cockpit-views#req:remote-envelope-is-untrusted: the schema versions, the shape asked for, every field held to the rule of its kind and name (see checkValue), the document's identity rules and the metrics history's rules. It does not look at which machine the envelope names: that is never used for placement, and a merger re-derives every id under the configured machine's key.
type EnvelopeMetrics ¶ added in v0.175.0
type EnvelopeMetrics struct {
Route string `json:"route"`
Samples []machinemetrics.Sample `json:"samples"`
Reason string `json:"reason,omitempty"`
}
EnvelopeMetrics is the exporting machine's own metrics: the route it came by (`local`, or `none` with a Reason) and the history, oldest first, whose last element is the latest sample.
type ExportDrops ¶ added in v0.175.0
type ExportDrops struct {
Repositories, Worktrees, PullRequests, Agents int
}
ExportDrops counts the entries of this machine an export left out, by kind. It holds numbers only: what was dropped is never named.
func ParseExportDrops ¶ added in v0.175.0
func ParseExportDrops(header string) ExportDrops
ParseExportDrops reads an ExportDropsHeader value. Anything but four counts within the document's range is no drops at all: a daemon that sends none (an older one) left nothing out of its answer.
func (ExportDrops) Header ¶ added in v0.175.0
func (d ExportDrops) Header() string
Header is the drops as ExportDropsHeader carries them.
func (ExportDrops) Plus ¶ added in v0.175.0
func (d ExportDrops) Plus(other ExportDrops) ExportDrops
Plus is the drops of two passes over the same entries.
func (ExportDrops) Total ¶ added in v0.175.0
func (d ExportDrops) Total() int
Total is the number of entries left out.
type ExportError ¶ added in v0.175.0
ExportError is what the export verb prints when it could not make an envelope.
func NewExportError ¶ added in v0.175.0
func NewExportError(code string) ExportError
NewExportError is the ExportError for code.
type GitGate ¶
GitGate says whether Git is new enough to be run against untrusted repositories at all: before 2.45 GIT_NO_LAZY_FETCH is ignored, so a repository's promisor remote could be contacted. An error says the version could not be read at all (the command failed or did not answer in time), which is not an answer: the snapshotter keeps Git off for that pass and asks again on the next, and stops asking once it has a definite answer.
type HTTPExporter ¶ added in v0.175.0
type HTTPExporter struct {
// contains filtered or unexported fields
}
HTTPExporter is the HTTP RemoteExporter.
func NewHTTPExporter ¶ added in v0.175.0
func NewHTTPExporter(now func() time.Time) *HTTPExporter
NewHTTPExporter is the HTTP exporter with the transport's timeouts, on the clock now (nil means time.Now).
func (*HTTPExporter) Export ¶ added in v0.175.0
func (e *HTTPExporter) Export(ctx context.Context, target RemoteTarget, metricsOnly bool) (Envelope, error)
Export reads target's envelope from its configured HTTP route. A target with no route, or whose address the hub address rule refuses, has no HTTP route and no request is made. A credential that is missing, not private or malformed is http_auth_failed, again with no request.
type HTTPRoute ¶ added in v0.175.0
HTTPRoute is one machine's HTTP route, from local configuration: the origin of its daemon-hosted hub and the private file that holds the machine bearer credential for it.
type Hardware ¶ added in v0.175.0
Hardware is what the machine entry of this machine reports of itself (cockpit-views#req:machine-fields): the operating system and architecture names, the CPU count and the boot time. Zero fields are omitted from the document. Nothing here is a process list, a path or an environment value.
func LocalHardware ¶ added in v0.175.0
func LocalHardware() Hardware
LocalHardware reads this machine's hardware facts. The boot time is the operating system's own and is zero where it cannot be read.
type HerdrActivity ¶ added in v0.175.0
type HerdrActivity struct {
Open func() (HerdrLister, error)
}
HerdrActivity is the ActivityCollector over herdr. Open returns the herdr client of a refresh, or an error when herdr is not installed; it is asked on every read, so installing herdr needs no restart.
func DefaultHerdrActivity ¶ added in v0.175.0
func DefaultHerdrActivity() HerdrActivity
DefaultHerdrActivity reads the herdr found through HERDR_BIN_PATH or PATH, with the herdr call bounded by activityTimeout.
func (HerdrActivity) Activity ¶ added in v0.175.0
Activity lists herdr's agents once and returns the status of each by its harness session id. An agent with no harness session id, or whose id two agents with different statuses share, is left out (the join is by id and is never guessed), and a status outside the five herdr documents reads as unknown.
type HerdrLister ¶ added in v0.175.0
HerdrLister is the one herdr call the cockpit makes: the list of the agents herdr hosts. *herdr.Client satisfies it. The cockpit never reads a pane's screen text (herdr.Client.AgentRead): that is content, which belongs to cockpit-actions.
type IdentityCollector ¶
type IdentityCollector interface {
Identity(ctx context.Context, repository discover.Repo) (string, error)
}
IdentityCollector names the repository a clone's origin points at, as the lower-case host/owner/name identity the lifecycle-hook worker puts in its receipts. The origin URL stays inside the collector: only the identity comes back, and it is empty for a clone with no origin or one that names no forge. Like the other Git readers it is asked only when the fingerprint moved.
type KindCount ¶
KindCount is the number of symbols of one kind. The kind is a short lower-case word that matched kindPattern.
type LinkedWorktree ¶
type LinkedWorktree struct {
// Path is the worktree's directory and Branch the branch it has checked
// out, empty when detached. Both stay inside the daemon.
Path string
Branch string
}
LinkedWorktree is one linked worktree a repository's Git state registers.
type LocalCodeIndex ¶
type LocalCodeIndex struct {
Reader *lifecyclehooks.FreshnessReader
// Git is the Git binary; empty means "git".
Git string
// Runner runs the Git commands; nil means the real runner.
Runner runner.Runner
}
LocalCodeIndex reads the receipts the lifecycle-hook worker writes. It stays indexer-agnostic: it knows executor names from the hooks configuration and nothing of what any of them produces, and it never starts one, opens an artifact, writes a file or fetches. Every Git command goes through the hardened helper.
func (LocalCodeIndex) Begin ¶
func (c LocalCodeIndex) Begin() (CodeIndexPass, error)
Begin reads the receipts and the queue.
type LocalCollectors ¶
type LocalCollectors struct {
// ProjectsRoot is the projects root and Home the WB home directory.
ProjectsRoot string
Home string
// IndexCachePath is where the clone inventory is cached between scans;
// empty means a fresh scan every refresh.
IndexCachePath string
// Git is the Git binary; empty means "git". A test supplies its own.
Git string
// CodeIndex reads the indexer receipts; nil means entries carry no code
// index.
CodeIndex *LocalCodeIndex
// CodeIndexProvider is the configured code-index provider; nil means none.
CodeIndexProvider CodeIndexProvider
// Runner runs the Git commands; nil means the real runner. A test supplies
// a fake, so the unit tier starts no process.
Runner runner.Runner
// DeclaredOwner reads a worktree's declared owner process liveness
// (worktrees.OwnerLive, OwnerGone or OwnerUnstated); nil means the Work Log
// journal's, read without writing (worktrees.DeclaredOwnerLiveReadOnly): one
// file read and a signal-zero check of each recorded process id.
DeclaredOwner func(worktree string) string
// ProcessStart observes when a process started; nil means
// daemon.ProcessStartTime (Linux only: elsewhere it reports false).
ProcessStart func(pid int) (time.Time, bool)
}
LocalCollectors reads this machine. Nothing it does contacts a network or writes inside a repository; the clone inventory cache, when IndexCachePath is set, is written outside every repository.
func (LocalCollectors) Branches ¶
func (c LocalCollectors) Branches(ctx context.Context, repository discover.Repo) ([]BranchRef, error)
Branches runs one `git for-each-ref` over refs/heads and refs/remotes, which only reads the repository's refs.
func (LocalCollectors) Collectors ¶
func (c LocalCollectors) Collectors(remote RemoteCollector) Collectors
Collectors is c as the snapshotter's local sources, with remote as the other machines' source (nil for none).
func (LocalCollectors) DefaultBranch ¶
DefaultBranch is the branch origin's HEAD names (refs/remotes/origin/HEAD), else the branch the clone has checked out, else empty.
func (LocalCollectors) GitUsable ¶
func (c LocalCollectors) GitUsable(ctx context.Context) (bool, error)
GitUsable reads `git version` through the hardened helper and reports whether it names Git 2.45 or newer. A command that fails is an error, not a Git that is too old.
func (LocalCollectors) Identity ¶
Identity reads `remote.origin.url` from the repository's own configuration through the hardened helper (a local, read-only read; Git exits 1 when the key is absent) and turns it into the worker's identity with the worker's own function. A URL that names no forge gives an empty identity, not an error.
func (LocalCollectors) PullRequests ¶
func (c LocalCollectors) PullRequests(ctx context.Context) ([]worktrees.RegisteredPullRequestBinding, error)
PullRequests lists the pull requests `wb pr create` recorded beside active Work Log claims. It reads local files and calls no forge.
func (LocalCollectors) Readme ¶
func (c LocalCollectors) Readme(ctx context.Context, repository discover.Repo, branch string) ([]byte, error)
Readme reads README.md at the tip of refs/heads/<branch> from the object store: the tree entry is looked up, must be a regular blob (mode 100644 or 100755, so never a symbolic link, tree or submodule), is measured before it is read, and is read by its object id. No path in the working tree is opened.
func (LocalCollectors) Record ¶
func (c LocalCollectors) Record(worktree string) (WorktreeRecord, bool)
Record reads the worktree's manifest, heartbeat and declared owner process. The reads only open files and probe a process id; a directory with no valid manifest is not a WB task worktree.
func (LocalCollectors) Repositories ¶
Repositories scans the projects root, reusing the cached inventory while its fingerprint holds.
func (LocalCollectors) Runs ¶
Runs lists the dispatched runs, each rendered so that a run whose owner is gone reads as abandoned instead of running.
func (LocalCollectors) Worktrees ¶
func (c LocalCollectors) Worktrees(ctx context.Context, repository discover.Repo) ([]LinkedWorktree, error)
Worktrees reads the linked worktrees from the administrative directories Git keeps under .git/worktrees: each holds the worktree's path (gitdir, relative to that directory when not absolute) and its HEAD. It runs no Git command. An entry whose gitdir is gone is a worktree being created or pruned, and one whose directory does not exist or lies outside the projects root and the worktree stores is not WB's; both are skipped. The path returned has no symbolic link in it.
type LocalStateCollector ¶
type LocalStateCollector struct {
Reader LocalStateReader
}
LocalStateCollector reads other machines' snapshots through a LocalStateReader. It is the only source of other machines, and it contacts nothing: a machine whose state store has no local copy knows no others.
func (LocalStateCollector) Machines ¶
func (c LocalStateCollector) Machines(ctx context.Context) ([]remotestate.Entry, error)
Machines reads the machines the local copy of the state store holds.
type LocalStateReader ¶
type LocalStateReader interface {
ReadLocal(ctx context.Context) ([]remotestate.Entry, error)
}
LocalStateReader reads other machines' snapshots from the copy of the remote state store this machine already holds, without fetching it.
type LocalTerminals ¶ added in v0.175.0
type LocalTerminals struct {
// ProjectsRoot resolves the WB homes; Home is one more home to read, which
// is usually the first of them.
ProjectsRoot string
Home string
// Homes, when set, are the only homes read: nothing is resolved from the
// projects root or the environment. A test names its own.
Homes []string
// Now is the clock of the racy-time rule; nil means time.Now.
Now func() time.Time
// contains filtered or unexported fields
}
LocalTerminals lists and reads this machine's sealed terminal records, the files <home>/worklogs/<task>/runs/<run>/terminals/<claim id>.json that `worktreeclaims.TerminalPorts.SealTerminal` writes, in every WB home the projects root resolves to (the current home and a legacy one). It only lists directories and reads files: it never writes, creates or locks anything, and a symbolic link is skipped rather than followed. The record type is worktreeclaims.TerminalRecord; no path or content leaves the daemon.
A terminal record is immutable and sealing one changes the modification time of its `terminals` directory (and a new run that of the task's `runs` directory), so List remembers each of those directories' listing with the modification time it saw and lists a directory again only when its time moved or is too recent to trust (within racyWindow of the listing, like Git's racy timestamps). An unchanged task costs two stats. Build one with NewLocalTerminals; it is used by one goroutine at a time.
func NewLocalTerminals ¶ added in v0.175.0
func NewLocalTerminals(projectsRoot, home string) *LocalTerminals
NewLocalTerminals is the source for the homes the projects root and home name.
func (*LocalTerminals) List ¶ added in v0.175.0
func (l *LocalTerminals) List(ctx context.Context, limit int) ([]TerminalFile, bool, error)
List walks the Work Log of every home and returns up to limit terminal files, saying whether more existed. A missing directory is an empty Work Log; any other unreadable directory is skipped.
func (*LocalTerminals) Read ¶ added in v0.175.0
func (*LocalTerminals) Read(key string) (worktreeclaims.TerminalRecord, error)
Read decodes the record at key, a path List returned.
type Machine ¶
type Machine struct {
Entry
WBVersion string `json:"wb_version,omitempty"`
RepositoryCount int `json:"repository_count"`
WorktreeCount int `json:"worktree_count"`
OS string `json:"os,omitempty"`
Arch string `json:"arch,omitempty"`
CPUCount int `json:"cpu_count,omitempty"`
BootTime time.Time `json:"boot_time,omitzero"`
Transport string `json:"transport,omitempty"`
RemoteError string `json:"remote_error,omitempty"`
ExportDropped int `json:"export_dropped,omitempty"`
// PublishError is the code of the last failed or degraded periodic publish
// of this machine's snapshot (cockpit-views#req:periodic-remote-publish),
// on this machine's own entry only: absent when it is healthy and when
// publishing is off.
PublishError string `json:"publish_error,omitempty"`
AgentsTruncated bool `json:"agents_truncated,omitempty"`
}
Machine is this machine or another machine the remote provider has a snapshot for.
OS, Arch, CPUCount and BootTime are the machine's hardware facts, for this machine and for another whose published snapshot carries them, and omitted otherwise. They are names and numbers: no process list, path or environment. The document carries no resource samples; the metrics route serves them.
Transport is the transport (`http` or `ssh`) that produced a machine's live-remote entries, and RemoteError the code of the last failed read of another machine (cockpit-views#req:remote-error-is-visible): one of remoteErrorCodes, never the remote's own text. ExportDropped is the number of that machine's entries its export left out, or this daemon cut at its caps (of a live export and of a published snapshot alike), and AgentsTruncated says its agents were cut. None of the four is ever set on this machine's own entry.
type MetricsAnswer ¶ added in v0.175.0
type MetricsAnswer struct {
Route string
FetchedAt *time.Time
Samples []machinemetrics.Sample
Reason string
Version uint64
}
MetricsAnswer is what one MetricsSource knows of a machine.
type MetricsResponse ¶ added in v0.175.0
type MetricsResponse struct {
Machine string `json:"machine"`
Route string `json:"route"`
FetchedAt *time.Time `json:"fetched_at,omitempty"`
Samples []machinemetrics.Sample `json:"samples"`
Reason string `json:"reason,omitempty"`
}
MetricsResponse is the body of the machine-metrics route (cockpit-views#req:machine-metrics-route). Samples is never null.
type MetricsSource ¶ added in v0.175.0
type MetricsSource interface {
MachineMetrics(machineID string) (MetricsAnswer, bool)
}
MetricsSource answers for the machines it holds metrics of, from memory only: it must never fetch, read a file or start anything. The contract:
- It returns false for a machine it knows nothing of, and the next source is asked (live remote, then cached, as cockpit-views#req:machine-metrics-route orders them); the first that returns true answers.
- Staleness is the source's job: a source whose data is too old to serve returns false rather than an old answer.
- Version must change whenever anything else in the answer does, and must not go back while the route stays the same: the route prepares the body once for each (route, version), never once for each request.
- Whatever it returns is untrusted: the route sanitizes every answer (see sanitizeMetrics) before it is marshalled.
This machine's own id is never put to the sources: it is always answered by the local sampler.
type Options ¶
type Options struct {
// Machine is this machine's name and Version its WB version. Login is the
// login this machine publishes its own snapshot under, when known at the
// start: the machine's own publication is skipped by login and name
// together, and by name and projects root while the login is not known.
Machine string
Version string
Login string
// LoginSource says the login once something has learned it (the daemon's
// periodic publisher resolves it for its first publish), and "" until then;
// nil means nothing will. It is read from memory on each read of the other
// machines and must not run anything. The snapshotter also learns the login
// from this machine's own publication in the store. Until the login is
// known, no published entry is taken for a configured machine's
// (cachedMachinesOf): a name alone could be another login's machine.
LoginSource func() string
// ProjectsRoot is this machine's projects root, which is compared (never
// emitted) with a snapshot's to recognise this machine's own publication
// when Login is not known.
ProjectsRoot string
// Collectors are the sources; a nil Remote means no other machines.
Collectors Collectors
// Interval is the refresh interval; zero or less means DefaultInterval.
Interval time.Duration
// Workers bounds the repositories read at once; zero or less means the
// CPU count, at most eight.
Workers int
// RepositoryTimeout bounds one repository's read; zero or less means 30s.
RepositoryTimeout time.Duration
// StopWait bounds how long stopping waits for a read of the other
// machines; zero or less means two seconds.
StopWait time.Duration
// ActivityTimeout bounds the herdr read of one refresh; zero or less means
// three seconds.
ActivityTimeout time.Duration
// ProviderTimeout bounds one code-index provider ask and ProviderBudget all
// the asks of one repository's read; zero or less means 20 s and 2 min.
ProviderTimeout time.Duration
ProviderBudget time.Duration
// Hardware is this machine's hardware facts for its machine entry; the zero
// value omits them. The daemon passes LocalHardware().
Hardware Hardware
// Sampler is this machine's metrics history. It alone answers for this
// machine's id on the machine-metrics route (`local`); nil means this machine
// has none (`none`). The snapshotter starts and stops it with itself.
Sampler *machinemetrics.Sampler
// Metrics are the sources of the machine-metrics route for other machines,
// asked in order (the live remote, then the cached source).
Metrics []MetricsSource
// Publisher publishes this machine's snapshot to the remote store after a
// successful pass (cockpit-views#req:periodic-remote-publish); nil, the
// default, publishes nothing. It is called off the pass, in a goroutine of
// its own, and decides for itself whether the time has come.
Publisher RemotePublisher
// Remotes are the other machines the local configuration names, each read
// in the background through Transports, in that order
// (cockpit-views#req:remote-exporter-transports). With no transport nothing
// is read. A target named as this machine is ignored: an export is never
// applied to this machine's own entries.
Remotes []RemoteTarget
Transports []RemoteTransport
// SSHRoutes are the SSH routes the local configuration gives other machines,
// by their configured key, whether or not the daemon reads them: an owner
// session's "Copy command" entries are built from them (MachineRoutes).
SSHRoutes map[string]SSHRoute
// RemoteTick delivers the ticks on which the background loop looks for a due
// export, as Tick does for the refresh; nil means a time.Ticker.
RemoteTick func(interval time.Duration) (<-chan time.Time, func())
// Compress compresses a stored body; nil means cockpit.Gzip. It runs once
// for each snapshot stored and once for each repository's branch list that
// is first asked for after a change, never per request; a test counts it.
Compress func([]byte) []byte
// Now is the clock; nil means time.Now.
Now func() time.Time
// Tick delivers the refresh ticks for an interval and a function that
// stops them; nil means a time.Ticker. A test supplies its own.
Tick func(interval time.Duration) (<-chan time.Time, func())
// Fingerprint computes a clone's fingerprint; nil means Fingerprint.
Fingerprint func(checkout string) (string, error)
// PullRequests observes the pull requests recorded locally on the
// snapshotter's own ticker; nil means none is observed (the daemon passes a
// *prwatch.Watcher). PullRequestLimit is the most observed per pass (zero
// or less means DefaultPullRequestLimit), PullRequestHourlyBudget the most
// observations in a rolling hour (zero or less means
// DefaultPullRequestHourlyBudget) and PullRequestTimeout
// the bound of one observation (zero or less means 30 s).
PullRequests PullRequestObserver
PullRequestLimit int
PullRequestHourlyBudget int
PullRequestTimeout time.Duration
// Terminals is this machine's sealed terminal records, the source of the
// throughput block; nil means the document has none. ThroughputInterval is
// the time between scans (zero or less means DefaultThroughputInterval),
// TerminalReadLimit the most records read in one scan and TerminalTotalLimit
// the most known at once (zero or less means the defaults).
Terminals TerminalRecords
ThroughputInterval time.Duration
TerminalReadLimit int
TerminalTotalLimit int
// Logf reports a refresh that failed, in whole or in part; nil discards.
Logf func(format string, args ...any)
}
Options configures a Snapshotter.
type ProviderStatistics ¶
type ProviderStatistics struct {
Indexed bool
Files int
Symbols int
Edges int
Kinds map[string]int
}
ProviderStatistics is what a provider reports for one checkout: whether an index exists, its totals, and the symbols by kind.
type PullRequest ¶
type PullRequest struct {
Entry
Repository string `json:"repository,omitempty"`
Worktree string `json:"worktree,omitempty"`
Branch string `json:"branch,omitempty"`
Number int `json:"number"`
State string `json:"state,omitempty"`
URL string `json:"url,omitempty"`
// The fields below come from the daemon's last successful observation of
// the pull request and are omitted until one has succeeded
// (cockpit-views#req:pull-request-fields). On a `live-remote` entry they,
// and State (`merged` included), are what the other machine's daemon reported
// of its own observation, validated and never observed here: Route says
// whose word they are. A `cached` entry has none of them. The counts and the
// verdict are pointers because zero and false are values. ChecksGreen is the
// observation's own verdict, not derived from the counts.
Mergeable string `json:"mergeable,omitempty"`
ChecksTotal *int `json:"checks_total,omitempty"`
ChecksPassed *int `json:"checks_passed,omitempty"`
ChecksFailed *int `json:"checks_failed,omitempty"`
ChecksSkipped *int `json:"checks_skipped,omitempty"`
ChecksPending *int `json:"checks_pending,omitempty"`
ChecksGreen *bool `json:"checks_green,omitempty"`
FailedCheck string `json:"failed_check,omitempty"`
CheckedAt time.Time `json:"checked_at,omitzero"`
}
PullRequest is one open pull request recorded locally, tied to its repository and, where one exists, its worktree. State is empty for a local record, which names the pull request but not its state until one is observed; Repository is empty when the record's repository slug matches no single local repository.
type PullRequestCollector ¶
type PullRequestCollector interface {
PullRequests(ctx context.Context) ([]worktrees.RegisteredPullRequestBinding, error)
}
PullRequestCollector lists the pull requests recorded locally against active Work Log claims, without any network call.
type PullRequestObserver ¶ added in v0.175.0
type PullRequestObserver interface {
Evaluate(ctx context.Context, binding worktrees.RegisteredPullRequestBinding) (prwatch.Outcome, error)
Forget(binding worktrees.RegisteredPullRequestBinding)
Retain(bindings []worktrees.RegisteredPullRequestBinding)
}
PullRequestObserver is the watcher the snapshotter runs for the pull requests recorded locally. *prwatch.Watcher is the production one; a test supplies a fake, so no test reaches GitHub. Evaluate takes one observation of a binding; Forget and Retain drop what the watcher remembers of one binding and of every binding not listed.
type ReadError ¶ added in v0.175.0
type ReadError struct {
// contains filtered or unexported fields
}
ReadError is a failure to read an envelope's bytes, as opposed to a refusal of them. Its message is fixed; Unwrap gives the cause.
type ReadmeCollector ¶
type ReadmeCollector interface {
Readme(ctx context.Context, repository discover.Repo, branch string) ([]byte, error)
}
ReadmeCollector reads the README.md committed at the tip of a branch from Git's object store, never from the working tree. It fails with errReadmeAbsent, errReadmeNotRegular or errReadmeTooLarge.
type RecordCollector ¶
type RecordCollector interface {
Record(worktree string) (WorktreeRecord, bool)
}
RecordCollector reads a worktree's own manifest and heartbeat. They live outside Git state, so the snapshotter asks on every refresh. The result is false for a directory that is not a WB task worktree.
type RemoteCollector ¶
type RemoteCollector interface {
Machines(ctx context.Context) ([]remotestate.Entry, error)
}
RemoteCollector reads the snapshots other machines published, from what this machine already holds locally.
type RemoteError ¶ added in v0.175.0
RemoteError is a failed export as a code of remoteErrorCodes. Fallback says whether the next transport may be tried after it (cockpit-views#req:remote-exporter-transports). It carries no text of the remote, no address and no credential.
func (*RemoteError) Error ¶ added in v0.175.0
func (e *RemoteError) Error() string
type RemoteExporter ¶ added in v0.175.0
type RemoteExporter interface {
Export(ctx context.Context, target RemoteTarget, metricsOnly bool) (Envelope, error)
}
RemoteExporter reads one machine's export envelope over one transport. It is given only what the local configuration holds for the target, and returns ErrNoRoute for a target it has no route to, a *RemoteError for a failure, and a *BadEnvelopeError for an envelope it refused. Whatever it returns is checked again by the caller: an exporter is not trusted to have validated.
type RemotePublisher ¶ added in v0.175.0
type RemotePublisher interface {
Publish(ctx context.Context, source remotestate.PublishSource)
Diagnostic() string
}
RemotePublisher is the periodic remote publisher the snapshotter hands the end of each successful pass to. The snapshotter is the source it asks for the extras it publishes and for the change token that lets it skip a scan. Diagnostic is the code of its last attempt ("" when healthy), which the document shows on this machine's entry as publish_error.
type RemoteTarget ¶ added in v0.175.0
RemoteTarget is one other machine the local configuration names. Machine is its key in session_move.targets, and the only thing that places its entries. HTTP is its HTTP route and SSH its SSH route, each nil when it has none.
type RemoteTransport ¶ added in v0.175.0
type RemoteTransport struct {
Name string
Exporter RemoteExporter
}
RemoteTransport is an exporter and the name its entries carry as `transport`.
type Repository ¶
type Repository struct {
Entry
Host string `json:"host,omitempty"`
Name string `json:"name"`
DefaultBranch string `json:"default_branch,omitempty"`
WorktreeCount int `json:"worktree_count"`
LocalBranchCount *int `json:"local_branch_count,omitempty"`
RemoteBranchCount *int `json:"remote_branch_count,omitempty"`
OpenPullRequestCount *int `json:"open_pull_request_count,omitempty"`
ActiveAgentCount *int `json:"active_agent_count,omitempty"`
Error string `json:"error,omitempty"`
// LastActivityAt is the newest local-branch activity time, omitted for an
// entry cached from another machine. RemoteURLWeb is the
// https://<host>/<owner>/<name> address built from the forge host and the
// name alone, only when both are safe (webURL); the origin URL itself is
// never in the document.
LastActivityAt time.Time `json:"last_activity_at,omitzero"`
RemoteURLWeb string `json:"remote_url_web,omitempty"`
// CodeIndex is one state per indexer configured for the repository. It is
// absent for a cached repository, whose published snapshot does not carry
// it, and for a local one with no indexer or whose state could not be told.
CodeIndex []CodeIndex `json:"code_index,omitempty"`
}
Repository is one repository with its counts. A count that is nil is not known for this entry: a cached repository has no branch or agent counts, because another machine's snapshot does not carry them. Error is a short code when the repository could not be read this pass; what it last held is kept.
type RepositoryCollector ¶
RepositoryCollector lists this machine's canonical clones.
type RunCollector ¶
RunCollector lists the dispatched agent runs with their state resolved.
type SSHExporter ¶ added in v0.175.0
type SSHExporter struct {
// contains filtered or unexported fields
}
SSHExporter is the SSH RemoteExporter.
func NewSSHExporter ¶ added in v0.175.0
func NewSSHExporter(find func() (string, error), runner remotessh.Runner, now func() time.Time, logf func(string, ...any)) *SSHExporter
NewSSHExporter is the SSH exporter over find (which finds the local ssh executable; the daemon's is remotessh.ResolveTrusted) and runner, on the clock now (nil means time.Now), logging to logf (nil means nowhere).
func (*SSHExporter) Export ¶ added in v0.175.0
func (e *SSHExporter) Export(ctx context.Context, target RemoteTarget, metricsOnly bool) (Envelope, error)
Export reads target's envelope by running the export verb on it over SSH. A target with no route, or whose route the address rule refuses, has no SSH route and no process is started.
type SSHRoute ¶ added in v0.175.0
SSHRoute is one machine's SSH route, from local configuration (session_move.targets.<machine>.ssh): the host, the optional login and the optional path of the remote wb.
type SessionCollector ¶
SessionCollector lists the registered agent sessions.
type Snapshotter ¶
type Snapshotter struct {
// contains filtered or unexported fields
}
Snapshotter builds the fleet document in the background and holds the last one. A request reads Document or Body and nothing else: it never reaches a collector, so it never waits for Git (cockpit#req:no-fleet-scan-on-the- request-path).
func New ¶
func New(options Options) *Snapshotter
New builds a Snapshotter that has taken no snapshot: Document is the empty warming-up document until the first repository completes.
func (*Snapshotter) Branches ¶ added in v0.175.0
func (s *Snapshotter) Branches(id string) (payload cockpit.Payload, found bool)
Branches returns the branch list of the local repository with id, prepared for serving, from the last scan and without running anything (cockpit-views#req: lazy-branches-route). A repository cached from another machine is known and has no branches here: the answer is an empty list and its reason. An id that is neither is not found.
func (*Snapshotter) ChangeToken ¶ added in v0.175.0
func (s *Snapshotter) ChangeToken() string
ChangeToken is the same string for as long as nothing the snapshotter observes of this machine's repositories and worktrees has changed: the change fingerprint of each clone (which it already computes to skip Git work) and the worktree and pull-request facts it reads on every pass outside Git (owner state, lifecycle, ahead and behind, pull-request state, and the last activity at remotestate.ActivityBucket granularity). It is empty, which opens the publisher's gate, until a full pass has completed and whenever a clone's fingerprint could not be computed.
The invariant is a property of the fields it takes, not of the whole snapshot: for those fields the token moves exactly when the published digest would (remotestate.Snapshot.Digest truncates the same last-activity time to the same bucket, so a heartbeat inside a bucket moves neither), and an edit that moves none of them (a new untracked file, an unstaged edit of a tracked file, which no fingerprint and no fact the snapshotter reads sees) waits for the next change of one, or for the keepalive.
func (*Snapshotter) CheckedAt ¶ added in v0.175.0
func (s *Snapshotter) CheckedAt() time.Time
CheckedAt is when the daemon last assembled the document and found the published one current (or published the one that had changed): the freshness of what a reader holds, which the body does not carry because a body that did not change keeps its bytes and its ETag. It is zero before the first look.
func (*Snapshotter) Export ¶ added in v0.175.0
func (s *Snapshotter) Export(metricsOnly bool) Envelope
Export is this machine's export envelope built in process from the last published document and the sampler, without running anything. It reads memory only.
func (*Snapshotter) ExportPayload ¶ added in v0.175.0
func (s *Snapshotter) ExportPayload(metricsOnly bool) (payload cockpit.Payload, failure string)
ExportPayload is this machine's export prepared for the hub route (cockpit-views#req:hub-export-route): the envelope encoded, compressed and tagged once for each version of the published document and of the metrics history, so a request copies bytes; and of its two halves only the one that moved is encoded again (the fleet half is validated and encoded once for each published document, ownFleetNow). failure is empty, or ErrorWarmingUp for a full export while the first pass has not ended (a partial fleet must not replace what a reader holds), or ErrorExportFailed when the envelope would not pass its own rules or its size bound, or when this machine's repositories could not be listed at all (Unlistable). It reads memory only.
func (*Snapshotter) MachineMetrics ¶ added in v0.175.0
func (s *Snapshotter) MachineMetrics(id string) (payload cockpit.Payload, found bool)
MachineMetrics returns the metrics answer for machine id prepared for serving, and found false for an id that is not in the fleet document. It reads memory only.
func (*Snapshotter) MachineRoutes ¶ added in v0.175.0
func (s *Snapshotter) MachineRoutes() []cockpit.MachineRoute
MachineRoutes is the SSH routes of the configured machines, by the ids their machine entries have in the document: the id of each published entry of the machine and, for one that is read live, its live id. It is what an owner session's "Copy command" entries are built from (cockpit-views#req:copy-the-command), and it is never part of the document, of an export or of anything an anonymous reader is sent.
func (*Snapshotter) Payload ¶ added in v0.175.0
func (s *Snapshotter) Payload() cockpit.Payload
Payload returns the last published document prepared for serving.
func (*Snapshotter) PublishExtras ¶ added in v0.175.0
func (s *Snapshotter) PublishExtras() remotestate.Extras
PublishExtras is this machine's agents and its latest metrics sample, as the publisher may add them to a snapshot: only the entries and the sample the document already carries, with the repository of an agent named by the repository's name (owner/name), never its id or its path.
func (*Snapshotter) Refresh ¶
func (s *Snapshotter) Refresh(ctx context.Context) error
Refresh runs one pass. The agents and the pull-request records are read beside it, every repository is read by a bounded worker pool, and the document is republished as repositories complete (at most every publishInterval), so it is readable while the first pass runs. A repository's Git state is read only when its fingerprint moved; its worktrees' manifests and heartbeats, which live outside Git, are read every pass. A repository or source that fails keeps what it last contributed, a repository carrying an error code, and the failures come back joined. The other machines' snapshots are read by their own goroutine, which neither delays the pass nor ends the warm-up. If the repositories cannot be listed the document says so in its error field and the agents and other machines are still read. The first full pass ends the warm-up; a cancelled pass does not. A listing that fails before any has worked ends it too, in the error state (the document is then empty and says why), and the first listing that works starts the warm-up of the first pass.
func (*Snapshotter) RefreshRepository ¶
func (s *Snapshotter) RefreshRepository(ctx context.Context, id string) error
RefreshRepository reads one repository now, its Git state included whatever its fingerprint, and republishes the document (at once outside a pass, within the publication rate during one), so the daemon can reflect a finished operation without waiting for the interval. A repository that could not be read is recorded with an error code, shown in the republished document, and its failure is returned.
It is the read a pass makes of each repository whose fingerprint moved (scanOne), and the on-request refresh that cockpit#req:snapshot-refresh requires the daemon to be able to make. No operation of the daemon completes inside it yet (the mutating routes are a later task), so outside a pass nothing but that capability's tests calls it.
func (*Snapshotter) Start ¶
func (s *Snapshotter) Start(ctx context.Context) (stop func())
Start refreshes now and then on every interval until the returned function is called or ctx ends, and runs this machine's metrics sampler, when it has one, and the background reads of the configured machines, when there are any, for the same time. The function stops the loops, waits for them, and then waits a short, bounded time for a read of the other machines still running; no repository read outlives it.
type TerminalFile ¶ added in v0.175.0
TerminalFile is one terminal record as a listing sees it. Key names it to the source and stays inside the daemon; Size and ModTime are its identity: an immutable record whose size and time are unchanged is not read again.
type TerminalRecords ¶ added in v0.175.0
type TerminalRecords interface {
List(ctx context.Context, limit int) (files []TerminalFile, truncated bool, err error)
Read(key string) (worktreeclaims.TerminalRecord, error)
}
TerminalRecords is the collector's only way to this machine's terminal records, so a test replaces it. List returns at most limit files and says whether more existed; Read decodes one. Both are read-only.
type Throughput ¶ added in v0.175.0
type Throughput struct {
WindowDays int `json:"window_days"`
PerDay []ThroughputDay `json:"per_day"`
Slowest []ThroughputTask `json:"slowest"`
MedianSeconds *int `json:"median_seconds,omitempty"`
P90Seconds *int `json:"p90_seconds,omitempty"`
Capped bool `json:"capped,omitempty"`
}
Throughput is the sealed-work throughput block. PerDay lists only the days with a sealing, oldest first; Slowest at most five finished tasks, longest first. Both are lists, never null. MedianSeconds and P90Seconds are the median and 90th percentile (nearest rank) of the finished tasks' durations in the window, absent when none finished. Capped says the collector hit one of its bounds, so the numbers cover part of the records.
type ThroughputDay ¶ added in v0.175.0
type ThroughputDay struct {
Date string `json:"date"`
Finished int `json:"finished"`
Dropped int `json:"dropped"`
Landed int `json:"landed,omitempty"`
}
ThroughputDay is the number of tasks sealed on one UTC date (YYYY-MM-DD): the finished ones and the dropped ones. A task counts once a day, as finished when any of its records that day is. Landed is how many of the finished were sealed `landed`, absent when none.
type ThroughputTask ¶ added in v0.175.0
type ThroughputTask struct {
Task string `json:"task"`
DurationSeconds int `json:"duration_seconds"`
LandedAt time.Time `json:"landed_at"`
}
ThroughputTask is one of the slowest finished tasks: its name, the seconds from its claim to its sealing and the sealing time. A task finished in several repositories appears once, with its longest duration.
type Worktree ¶
type Worktree struct {
Entry
Repository string `json:"repository"`
Name string `json:"name"`
Task string `json:"task"`
Stream string `json:"stream,omitempty"`
Branch string `json:"branch"`
Lifecycle string `json:"lifecycle,omitempty"`
OwnerState string `json:"owner_state,omitempty"`
LastActivityAt time.Time `json:"last_activity_at,omitzero"`
Ahead *int `json:"ahead,omitempty"`
Behind *int `json:"behind,omitempty"`
UpstreamGone bool `json:"upstream_gone,omitempty"`
HasUpstream *bool `json:"has_upstream,omitempty"`
// CodeIndex is as on Repository.
CodeIndex []CodeIndex `json:"code_index,omitempty"`
}
Worktree is one WB task worktree. Repository is the id of its repository. Name is its task, never a path. Ahead, Behind, UpstreamGone and HasUpstream are the sync facts of its branch, each omitted when unknown. A `local` entry carries what this daemon observed. A `live-remote` entry carries them, and its lifecycle and owner state, as the other machine reported them: they are that machine's word, not an observation of this one, and Route is what says so (no entry with another route ever carries an id or the machine id of this machine's entries, so a client that reads a fact as "observed here" only from a `local` entry cannot be misled). A `cached` entry has no sync facts.
type WorktreeCollector ¶
type WorktreeCollector interface {
Worktrees(ctx context.Context, repository discover.Repo) ([]LinkedWorktree, error)
}
WorktreeCollector lists the linked worktrees one repository's Git state registers. It reads Git state, so the snapshotter asks only when the repository's fingerprint moved.
type WorktreeRecord ¶
type WorktreeRecord struct {
Task string
Branch string
CreatedAt time.Time
HeartbeatAt time.Time
Owner string
// OwnerPID is the process id of the live declared owner, 0 when the owner
// is gone or unstated. It stays inside the daemon: it joins a session to
// the worktree its owner process holds and is never emitted.
OwnerPID int
// OwnerAgent is the agent the live owner declared (runtime, or runtime/id) and
// OwnerStarted when its process started, zero when the platform cannot say.
OwnerAgent string
OwnerStarted time.Time
}
WorktreeRecord is what a WB worktree records about itself locally: its manifest, its heartbeat and the liveness of its declared owner process. Owner is worktrees.OwnerLive, worktrees.OwnerGone or worktrees.OwnerUnstated (no owner process recorded).