package history import ( "bufio" "encoding/json" "errors" "fmt" "os" "path/filepath" "strings" "time" ) type CapacityDiskPoint struct { Mount string `json:"mount"` UsedBytes uint64 `json:"used_bytes"` TotalBytes uint64 `json:"total_bytes"` } type CapacitySnapshot struct { Timestamp time.Time `json:"timestamp"` Disks []CapacityDiskPoint `json:"disks,omitempty"` } type CapacityStore struct { dir string } func NewCapacityStore(baseDir string) *CapacityStore { if strings.TrimSpace(baseDir) == "" { baseDir = "." } return &CapacityStore{dir: filepath.Join(baseDir, "history", "capacity")} } func (s *CapacityStore) Path(clusterID string) string { return filepath.Join(s.dir, sanitizeClusterID(clusterID)+".jsonl") } func (s *CapacityStore) Append(clusterID string, snap CapacitySnapshot) error { if strings.TrimSpace(clusterID) == "" { return errors.New("empty cluster id") } if snap.Timestamp.IsZero() { snap.Timestamp = time.Now().UTC() } if len(snap.Disks) == 0 { return nil } 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 *CapacityStore) Load(clusterID string, since time.Time) ([]CapacitySnapshot, 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 []CapacitySnapshot{}, nil } return nil, err } defer f.Close() sc := bufio.NewScanner(f) sc.Buffer(make([]byte, 0, 64*1024), 8*1024*1024) out := make([]CapacitySnapshot, 0, 512) for sc.Scan() { line := strings.TrimSpace(sc.Text()) if line == "" { continue } var snap CapacitySnapshot if err := json.Unmarshal([]byte(line), &snap); err != nil { continue } if !since.IsZero() && snap.Timestamp.Before(since) { continue } if len(snap.Disks) == 0 { continue } out = append(out, snap) } if err := sc.Err(); err != nil { return nil, fmt.Errorf("scan capacity: %w", err) } return out, nil }