Files
pxmon/internal/history/availability_store.go
T
2026-06-16 21:52:10 +04:00

93 lines
2.2 KiB
Go

package history
import (
"bufio"
"encoding/json"
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"time"
)
type AvailabilitySnapshot struct {
Timestamp time.Time `json:"timestamp"`
ClusterUp bool `json:"cluster_up"`
VMStates map[string]string `json:"vm_states,omitempty"`
Error string `json:"error,omitempty"`
}
type AvailabilityStore struct {
dir string
}
func NewAvailabilityStore(baseDir string) *AvailabilityStore {
if strings.TrimSpace(baseDir) == "" {
baseDir = "."
}
return &AvailabilityStore{dir: filepath.Join(baseDir, "history", "availability")}
}
func (s *AvailabilityStore) Path(clusterID string) string {
return filepath.Join(s.dir, sanitizeClusterID(clusterID)+".jsonl")
}
func (s *AvailabilityStore) Append(clusterID string, snap AvailabilitySnapshot) error {
if strings.TrimSpace(clusterID) == "" {
return errors.New("empty cluster id")
}
if snap.Timestamp.IsZero() {
snap.Timestamp = time.Now().UTC()
}
if err := os.MkdirAll(s.dir, 0o700); err != nil {
return err
}
f, err := os.OpenFile(s.Path(clusterID), os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o600)
if err != nil {
return err
}
defer f.Close()
b, err := json.Marshal(snap)
if err != nil {
return err
}
_, err = f.Write(append(b, '\n'))
return err
}
func (s *AvailabilityStore) Load(clusterID string, since time.Time) ([]AvailabilitySnapshot, error) {
if strings.TrimSpace(clusterID) == "" {
return nil, errors.New("empty cluster id")
}
f, err := os.Open(s.Path(clusterID))
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return []AvailabilitySnapshot{}, nil
}
return nil, err
}
defer f.Close()
sc := bufio.NewScanner(f)
sc.Buffer(make([]byte, 0, 64*1024), 8*1024*1024)
out := make([]AvailabilitySnapshot, 0, 512)
for sc.Scan() {
line := strings.TrimSpace(sc.Text())
if line == "" {
continue
}
var snap AvailabilitySnapshot
if err := json.Unmarshal([]byte(line), &snap); err != nil {
continue
}
if !since.IsZero() && snap.Timestamp.Before(since) {
continue
}
out = append(out, snap)
}
if err := sc.Err(); err != nil {
return nil, fmt.Errorf("scan availability: %w", err)
}
return out, nil
}