package latest import "time" // LaneState is the administrative page's view of one Poll Lane (issue #102): // what the Lane's last pass saw. Due, Gap and Checked are filled in as the // pass computes them; a pass that returned before reaching a figure (refusal // backoff, sidecar down) carries the previous pass's figures forward rather // than overwriting them with zeroes the page would state as fact. type LaneState struct { Site string Due int LastRun time.Time Gap time.Duration // Checked is how many Series this pass actually read. A Lane with Series // due and nothing checked has stopped working; one with nothing due is // merely quiet, and the page must not draw the two the same (story 13). Checked int Clamped bool Refusing bool Browser bool } // Status is the owner's page snapshot of the whole poller (issue #102). type Status struct { Lanes []LaneState BrowserConfigured bool BrowserReachable bool } // LaneStatus returns a copy of the poller's Lane state for the owner's page. // Only Sites that have completed a pass appear — a restart therefore renders // "no data yet" instead of confident zeroes — in the same order Run iterates. // Refusing is derived at snapshot time from the refusal backoff, not stored, // so a Lane that cooled down between passes reports false without a new pass. // BrowserReachable mirrors the Lanes' own gate: the sidecar is down only // within the refuseBackoff window since its last loss. func (p *Poller) LaneStatus() Status { p.mu.Lock() defer p.mu.Unlock() lanes := make([]LaneState, 0, len(p.laneStates)) now := p.Now() for _, name := range laneNames() { st, ok := p.laneStates[name] if !ok { continue } st.Refusing = now.Before(p.refuseUntil[name]) lanes = append(lanes, st) } configured := p.BrowserFetch != nil reachable := configured if reachable && !p.browserDownAt.IsZero() && now.Sub(p.browserDownAt) < refuseBackoff { reachable = false } return Status{Lanes: lanes, BrowserConfigured: configured, BrowserReachable: reachable} } // recordLaneState stores one Lane's last pass for LaneStatus. Called deferred // from runLanePass so every return path records, even a pass that refused. // A pass that never reached the pace (Gap zero) keeps the last pass's figures: // the Lane's due count and gap did not become zero because this pass declined // to look, and the row's own marks say why it declined. func (p *Poller) recordLaneState(st LaneState) { p.mu.Lock() defer p.mu.Unlock() if p.laneStates == nil { p.laneStates = make(map[string]LaneState) } if prev, ok := p.laneStates[st.Site]; ok && st.Gap == 0 { st.Due, st.Gap, st.Clamped, st.Checked = prev.Due, prev.Gap, prev.Clamped, prev.Checked } p.laneStates[st.Site] = st }