Files
seaweedfs/sw-block/engine/replication/observe.go
T
pingqiuandClaude Opus 4.6 7436b3b79c feat: add integration closure and observability (Phase 05 Slice 4)
New files:
- observe.go: RegistryStatus, SenderStatus, RecoveryLog for debugging
- integration_test.go: V2-boundary integration tests through real
  engine entry path

Observability:
- Registry.Status() returns full snapshot: per-sender state, session
  snapshots, counts by category (InSync, Recovering, Rebuilding)
- RecoveryLog: append-only event log for recovery lifecycle debugging

Integration tests (6):
- ChangedAddress_FullFlow: initial recovery → address change →
  sender preserved → new session → recovery with proof
- NeedsRebuild_ThenRebuildAssignment: catch-up fails → NeedsRebuild
  → rebuild assignment → history-driven source → InSync
- EpochBump_DuringRecovery: mid-recovery epoch bump → old session
  rejected → new assignment at new epoch → InSync
- MultiReplica_MixedOutcomes: 3 replicas, 3 outcomes via
  RetainedHistory proofs, registry status verified
- RegistryStatus_Snapshot: observability snapshot structure
- RecoveryLog: event recording and filtering

Engine module at 54 tests (12 + 18 + 18 + 6).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 00:15:46 -07:00

97 lines
2.2 KiB
Go

package replication
// SenderStatus is a read-only observability snapshot of one sender.
type SenderStatus struct {
ReplicaID string
Endpoint Endpoint
Epoch uint64
State ReplicaState
Stopped bool
Session *SessionSnapshot // nil if no session
}
// RegistryStatus is a read-only observability snapshot of the entire registry.
type RegistryStatus struct {
Senders []SenderStatus
TotalCount int
InSync int
Recovering int
Degraded int
Rebuilding int
}
// Status returns a full observability snapshot of the registry.
func (r *Registry) Status() RegistryStatus {
r.mu.RLock()
defer r.mu.RUnlock()
status := RegistryStatus{TotalCount: len(r.senders)}
for _, s := range r.All() {
ss := SenderStatus{
ReplicaID: s.ReplicaID(),
Endpoint: s.Endpoint(),
Epoch: s.Epoch(),
State: s.State(),
Stopped: s.Stopped(),
Session: s.SessionSnapshot(),
}
status.Senders = append(status.Senders, ss)
switch ss.State {
case StateInSync:
status.InSync++
case StateCatchingUp, StateConnecting:
status.Recovering++
case StateDegraded:
status.Degraded++
case StateNeedsRebuild:
status.Rebuilding++
}
}
return status
}
// RecoveryEvent records a significant recovery lifecycle event for debugging.
type RecoveryEvent struct {
ReplicaID string
SessionID uint64
Event string
Detail string
}
// RecoveryLog collects recovery events for observability.
type RecoveryLog struct {
events []RecoveryEvent
}
// NewRecoveryLog creates an empty recovery log.
func NewRecoveryLog() *RecoveryLog {
return &RecoveryLog{}
}
// Record adds an event to the log.
func (rl *RecoveryLog) Record(replicaID string, sessionID uint64, event, detail string) {
rl.events = append(rl.events, RecoveryEvent{
ReplicaID: replicaID,
SessionID: sessionID,
Event: event,
Detail: detail,
})
}
// Events returns all recorded events.
func (rl *RecoveryLog) Events() []RecoveryEvent {
return rl.events
}
// EventsFor returns events for a specific replica.
func (rl *RecoveryLog) EventsFor(replicaID string) []RecoveryEvent {
var out []RecoveryEvent
for _, e := range rl.events {
if e.ReplicaID == replicaID {
out = append(out, e)
}
}
return out
}