ingotctl, HTTP layer, and self-instrumentation

Add cmd/ingotctl with four subcommands: blocks (list with stats),
inspect (series/postings dump), chunks (decode raw samples by ref), and
fsck (CRC and index integrity validation across all blocks).

Add cmd/ingothttp with JSON query endpoints: /api/v1/query_range
(Prometheus-style matrix response), /api/v1/read (matcher-based read
requests), and /epi/v1/status (DB stats snapshot). Uses JSON instead of
protobuf to maintain zero dependencies.

Add self-instrumentation via the normal Appender path so metrics are
queryable with the same API: ingot_head_series,
ingot_head_chunks_active, ingot_blocks_total, ingot_compactions_total,
ingot_wal_fsync_duration_seconds.

Supporting changes: block.Validate() and block.ReadMeta() exports,
Head.Stats() for series/chunk counts, WAL.LastSyncDuration() with timed
fsync tracking, DB.Stats() for the HTTP status endpoint.
This commit is contained in:
2026-07-04 17:40:50 -04:00
parent 0356f2e082
commit 30a93a868e
12 changed files with 1878 additions and 2 deletions
+19 -2
View File
@@ -3,6 +3,7 @@ package wal
import (
"os"
"sync"
"sync/atomic"
"time"
)
@@ -46,6 +47,8 @@ type WAL struct {
segmentOff int64
buf []byte
lastSyncDur atomic.Int64 // nanoseconds of last fsync
done chan struct{}
wg sync.WaitGroup
}
@@ -164,7 +167,21 @@ func (w *WAL) Replay() (*Reader, error) {
func (w *WAL) Sync() error {
w.mu.Lock()
defer w.mu.Unlock()
return w.segment.Sync()
return w.timedSync()
}
// LastSyncDuration returns the duration of the most recent fsync in seconds.
func (w *WAL) LastSyncDuration() float64 {
ns := w.lastSyncDur.Load()
return float64(ns) / 1e9
}
// timedSync fsyncs the segment and records the duration. Caller must hold w.mu.
func (w *WAL) timedSync() error {
start := time.Now()
err := w.segment.Sync()
w.lastSyncDur.Store(int64(time.Since(start)))
return err
}
// Truncate deletes all segments with index less than below.
@@ -244,7 +261,7 @@ func (w *WAL) syncLoop(interval time.Duration) {
return
case <-ticker.C:
w.mu.Lock()
w.segment.Sync()
w.timedSync()
w.mu.Unlock()
}
}