9bb4b09a04
CI / Lint + build + test (push) Has been cancelled
Post-repair hardware validation pipeline for Proxmox cluster hosts. Go orchestrator + in-image agent + mkosi live image + bundled dnsmasq PXE + SQLite + HTMX/SSE UI + notify registry + janitor + full docs.
86 lines
2.4 KiB
Go
86 lines
2.4 KiB
Go
package store
|
||
|
||
import (
|
||
"context"
|
||
"database/sql"
|
||
"fmt"
|
||
"time"
|
||
|
||
"vetting/internal/model"
|
||
)
|
||
|
||
// Measurements persists timestamped numeric samples: temps, fan speeds,
|
||
// PSU voltages, fio IOPS, iperf throughput, SMART attributes. The schema
|
||
// stores (kind, key, value, unit) so Phase 5 reports can group freely
|
||
// without new tables per source.
|
||
type Measurements struct {
|
||
DB *sql.DB
|
||
}
|
||
|
||
func (m *Measurements) Create(ctx context.Context, in model.Measurement) (int64, error) {
|
||
if in.TS.IsZero() {
|
||
in.TS = time.Now().UTC()
|
||
}
|
||
res, err := m.DB.ExecContext(ctx, `
|
||
INSERT INTO measurements(run_id, stage_id, ts, kind, key, value, unit)
|
||
VALUES(?,?,?,?,?,?,?)
|
||
`, in.RunID, nullInt64(in.StageID), in.TS, in.Kind, in.Key, in.Value, in.Unit)
|
||
if err != nil {
|
||
return 0, fmt.Errorf("insert measurement: %w", err)
|
||
}
|
||
return res.LastInsertId()
|
||
}
|
||
|
||
// CreateBatch inserts a batch in one transaction. The sensor endpoint
|
||
// hands us ~5–20 samples per tick; a single commit keeps SQLite happy.
|
||
func (m *Measurements) CreateBatch(ctx context.Context, rows []model.Measurement) error {
|
||
if len(rows) == 0 {
|
||
return nil
|
||
}
|
||
tx, err := m.DB.BeginTx(ctx, nil)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
defer func() { _ = tx.Rollback() }()
|
||
now := time.Now().UTC()
|
||
for _, r := range rows {
|
||
if r.TS.IsZero() {
|
||
r.TS = now
|
||
}
|
||
if _, err := tx.ExecContext(ctx, `
|
||
INSERT INTO measurements(run_id, stage_id, ts, kind, key, value, unit)
|
||
VALUES(?,?,?,?,?,?,?)
|
||
`, r.RunID, nullInt64(r.StageID), r.TS, r.Kind, r.Key, r.Value, r.Unit); err != nil {
|
||
return fmt.Errorf("insert measurement: %w", err)
|
||
}
|
||
}
|
||
return tx.Commit()
|
||
}
|
||
|
||
// ListForRun returns all measurements for a run. Callers filter by kind
|
||
// in memory; the row count is small per run (≈thousands).
|
||
func (m *Measurements) ListForRun(ctx context.Context, runID int64) ([]model.Measurement, error) {
|
||
rows, err := m.DB.QueryContext(ctx, `
|
||
SELECT id, run_id, stage_id, ts, kind, key, value, COALESCE(unit,'')
|
||
FROM measurements WHERE run_id = ? ORDER BY ts, id
|
||
`, runID)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
defer rows.Close()
|
||
var out []model.Measurement
|
||
for rows.Next() {
|
||
var meas model.Measurement
|
||
var stageID sql.NullInt64
|
||
if err := rows.Scan(&meas.ID, &meas.RunID, &stageID, &meas.TS, &meas.Kind, &meas.Key, &meas.Value, &meas.Unit); err != nil {
|
||
return nil, err
|
||
}
|
||
if stageID.Valid {
|
||
v := stageID.Int64
|
||
meas.StageID = &v
|
||
}
|
||
out = append(out, meas)
|
||
}
|
||
return out, rows.Err()
|
||
}
|