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
+28
View File
@@ -8,6 +8,7 @@ import (
"path/filepath"
"sort"
"sync"
"sync/atomic"
"time"
"git.dvdt.dev/david/ingot/internal/block"
@@ -37,6 +38,9 @@ type DB struct {
compactCtx context.Context
compactCancel context.CancelFunc
compactWg sync.WaitGroup
compactionCount atomic.Int64 // incremented on each successful compaction
metricsR metricsRefs // cached series refs for self-instrumentation
}
// Options configures a DB.
@@ -237,6 +241,7 @@ func (db *DB) RunCompaction() error {
}
}
db.compactionCount.Add(1)
return nil
}
@@ -292,6 +297,7 @@ func (db *DB) compactLoop() {
case <-db.compactCtx.Done():
return
case <-ticker.C:
db.collectMetrics()
db.autoFlush()
db.RunCompaction()
db.ApplyRetention()
@@ -306,6 +312,28 @@ func (db *DB) autoFlush() {
db.FlushOlderThan(cutoff)
}
// DBStats holds summary statistics for the database.
type DBStats struct {
HeadSeries int
HeadChunks int
Blocks int
Compactions int
}
// Stats returns a snapshot of database statistics.
func (db *DB) Stats() DBStats {
hs := db.head.Stats()
db.mu.RLock()
numBlocks := len(db.blocks)
db.mu.RUnlock()
return DBStats{
HeadSeries: hs.NumSeries,
HeadChunks: hs.NumActiveChunks,
Blocks: numBlocks,
Compactions: int(db.compactionCount.Load()),
}
}
// Close closes the DB, releasing all resources.
func (db *DB) Close() error {
db.compactCancel()