mirror of
https://github.com/prometheus/prometheus.git
synced 2025-08-06 22:27:17 +02:00
Merge pull request #16695 from sujalshah-bit/block_endpoint
api: Create `/status/tsdb/blocks` endpoint.
This commit is contained in:
commit
2e709c6567
@ -1756,6 +1756,21 @@ func (s *readyStorage) CleanTombstones() error {
|
|||||||
return tsdb.ErrNotReady
|
return tsdb.ErrNotReady
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BlockMetas implements the api_v1.TSDBAdminStats and api_v2.TSDBAdmin interfaces.
|
||||||
|
func (s *readyStorage) BlockMetas() ([]tsdb.BlockMeta, error) {
|
||||||
|
if x := s.get(); x != nil {
|
||||||
|
switch db := x.(type) {
|
||||||
|
case *tsdb.DB:
|
||||||
|
return db.BlockMetas(), nil
|
||||||
|
case *agent.DB:
|
||||||
|
return nil, agent.ErrUnsupported
|
||||||
|
default:
|
||||||
|
panic(fmt.Sprintf("unknown storage type %T", db))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, tsdb.ErrNotReady
|
||||||
|
}
|
||||||
|
|
||||||
// Delete implements the api_v1.TSDBAdminStats and api_v2.TSDBAdmin interfaces.
|
// Delete implements the api_v1.TSDBAdminStats and api_v2.TSDBAdmin interfaces.
|
||||||
func (s *readyStorage) Delete(ctx context.Context, mint, maxt int64, ms ...*labels.Matcher) error {
|
func (s *readyStorage) Delete(ctx context.Context, mint, maxt int64, ms ...*labels.Matcher) error {
|
||||||
if x := s.get(); x != nil {
|
if x := s.get(); x != nil {
|
||||||
|
@ -1355,6 +1355,64 @@ curl http://localhost:9090/api/v1/status/tsdb
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
*New in v3.6.0*
|
||||||
|
|
||||||
|
### TSDB Blocks
|
||||||
|
|
||||||
|
**NOTE**: This endpoint is **experimental** and might change in the future. The endpoint name and the exact format of the returned data may change between Prometheus versions. The **exact metadata returned** by this endpoint is an implementation detail and may change in future Prometheus versions.
|
||||||
|
|
||||||
|
The following endpoint returns the list of currently loaded TSDB blocks and their metadata.
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/v1/status/tsdb/blocks
|
||||||
|
```
|
||||||
|
|
||||||
|
This endpoint returns the following information for each block:
|
||||||
|
|
||||||
|
- `ulid`: Unique ID of the block.
|
||||||
|
- `minTime`: Minimum timestamp (in milliseconds) of the block.
|
||||||
|
- `maxTime`: Maximum timestamp (in milliseconds) of the block.
|
||||||
|
- `stats`:
|
||||||
|
- `numSeries`: Number of series in the block.
|
||||||
|
- `numSamples`: Number of samples in the block.
|
||||||
|
- `numChunks`: Number of chunks in the block.
|
||||||
|
- `compaction`:
|
||||||
|
- `level`: The compaction level of the block.
|
||||||
|
- `sources`: List of ULIDs of source blocks used to compact this block.
|
||||||
|
- `version`: The block version.
|
||||||
|
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl http://localhost:9090/api/v1/status/tsdb/blocks
|
||||||
|
```
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"status": "success",
|
||||||
|
"data": {
|
||||||
|
"blocks": [
|
||||||
|
{
|
||||||
|
"ulid": "01JZ8JKZY6XSK3PTDP9ZKRWT60",
|
||||||
|
"minTime": 1750860620060,
|
||||||
|
"maxTime": 1750867200000,
|
||||||
|
"stats": {
|
||||||
|
"numSamples": 13701,
|
||||||
|
"numSeries": 716,
|
||||||
|
"numChunks": 716
|
||||||
|
},
|
||||||
|
"compaction": {
|
||||||
|
"level": 1,
|
||||||
|
"sources": [
|
||||||
|
"01JZ8JKZY6XSK3PTDP9ZKRWT60"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"version": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
*New in v2.15*
|
*New in v2.15*
|
||||||
|
|
||||||
### WAL Replay Stats
|
### WAL Replay Stats
|
||||||
|
10
tsdb/db.go
10
tsdb/db.go
@ -1074,6 +1074,16 @@ func (db *DB) Dir() string {
|
|||||||
return db.dir
|
return db.dir
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BlockMetas returns the list of metadata for all blocks.
|
||||||
|
func (db *DB) BlockMetas() []BlockMeta {
|
||||||
|
blocks := db.Blocks()
|
||||||
|
metas := make([]BlockMeta, 0, len(blocks))
|
||||||
|
for _, b := range blocks {
|
||||||
|
metas = append(metas, b.Meta())
|
||||||
|
}
|
||||||
|
return metas
|
||||||
|
}
|
||||||
|
|
||||||
func (db *DB) run(ctx context.Context) {
|
func (db *DB) run(ctx context.Context) {
|
||||||
defer close(db.donec)
|
defer close(db.donec)
|
||||||
|
|
||||||
|
@ -186,6 +186,7 @@ type TSDBAdminStats interface {
|
|||||||
Snapshot(dir string, withHead bool) error
|
Snapshot(dir string, withHead bool) error
|
||||||
Stats(statsByLabelName string, limit int) (*tsdb.Stats, error)
|
Stats(statsByLabelName string, limit int) (*tsdb.Stats, error)
|
||||||
WALReplayStatus() (tsdb.WALReplayStatus, error)
|
WALReplayStatus() (tsdb.WALReplayStatus, error)
|
||||||
|
BlockMetas() ([]tsdb.BlockMeta, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type QueryOpts interface {
|
type QueryOpts interface {
|
||||||
@ -411,6 +412,7 @@ func (api *API) Register(r *route.Router) {
|
|||||||
r.Get("/status/buildinfo", wrap(api.serveBuildInfo))
|
r.Get("/status/buildinfo", wrap(api.serveBuildInfo))
|
||||||
r.Get("/status/flags", wrap(api.serveFlags))
|
r.Get("/status/flags", wrap(api.serveFlags))
|
||||||
r.Get("/status/tsdb", wrapAgent(api.serveTSDBStatus))
|
r.Get("/status/tsdb", wrapAgent(api.serveTSDBStatus))
|
||||||
|
r.Get("/status/tsdb/blocks", wrapAgent(api.serveTSDBBlocks))
|
||||||
r.Get("/status/walreplay", api.serveWALReplayStatus)
|
r.Get("/status/walreplay", api.serveWALReplayStatus)
|
||||||
r.Get("/notifications", api.notifications)
|
r.Get("/notifications", api.notifications)
|
||||||
r.Get("/notifications/live", api.notificationsSSE)
|
r.Get("/notifications/live", api.notificationsSSE)
|
||||||
@ -1747,6 +1749,19 @@ func TSDBStatsFromIndexStats(stats []index.Stat) []TSDBStat {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (api *API) serveTSDBBlocks(_ *http.Request) apiFuncResult {
|
||||||
|
blockMetas, err := api.db.BlockMetas()
|
||||||
|
if err != nil {
|
||||||
|
return apiFuncResult{nil, &apiError{errorInternal, fmt.Errorf("error getting block metadata: %w", err)}, nil, nil}
|
||||||
|
}
|
||||||
|
|
||||||
|
return apiFuncResult{
|
||||||
|
data: map[string][]tsdb.BlockMeta{
|
||||||
|
"blocks": blockMetas,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (api *API) serveTSDBStatus(r *http.Request) apiFuncResult {
|
func (api *API) serveTSDBStatus(r *http.Request) apiFuncResult {
|
||||||
limit := 10
|
limit := 10
|
||||||
if s := r.FormValue("limit"); s != "" {
|
if s := r.FormValue("limit"); s != "" {
|
||||||
|
@ -15,6 +15,7 @@ package v1
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
@ -31,6 +32,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
jsoniter "github.com/json-iterator/go"
|
jsoniter "github.com/json-iterator/go"
|
||||||
|
"github.com/oklog/ulid/v2"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
config_util "github.com/prometheus/common/config"
|
config_util "github.com/prometheus/common/config"
|
||||||
"github.com/prometheus/common/model"
|
"github.com/prometheus/common/model"
|
||||||
@ -3793,10 +3795,15 @@ func assertAPIResponseMetadataLen(t *testing.T, got interface{}, expLen int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type fakeDB struct {
|
type fakeDB struct {
|
||||||
err error
|
err error
|
||||||
|
blockMetas []tsdb.BlockMeta
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *fakeDB) CleanTombstones() error { return f.err }
|
func (f *fakeDB) CleanTombstones() error { return f.err }
|
||||||
|
|
||||||
|
func (f *fakeDB) BlockMetas() ([]tsdb.BlockMeta, error) {
|
||||||
|
return f.blockMetas, nil
|
||||||
|
}
|
||||||
func (f *fakeDB) Delete(context.Context, int64, int64, ...*labels.Matcher) error { return f.err }
|
func (f *fakeDB) Delete(context.Context, int64, int64, ...*labels.Matcher) error { return f.err }
|
||||||
func (f *fakeDB) Snapshot(string, bool) error { return f.err }
|
func (f *fakeDB) Snapshot(string, bool) error { return f.err }
|
||||||
func (f *fakeDB) Stats(statsByLabelName string, limit int) (_ *tsdb.Stats, retErr error) {
|
func (f *fakeDB) Stats(statsByLabelName string, limit int) (_ *tsdb.Stats, retErr error) {
|
||||||
@ -4122,6 +4129,45 @@ func TestRespondSuccess_DefaultCodecCannotEncodeResponse(t *testing.T) {
|
|||||||
require.JSONEq(t, `{"status":"error","errorType":"not_acceptable","error":"cannot encode response as application/default-format"}`, string(body))
|
require.JSONEq(t, `{"status":"error","errorType":"not_acceptable","error":"cannot encode response as application/default-format"}`, string(body))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestServeTSDBBlocks(t *testing.T) {
|
||||||
|
blockMeta := tsdb.BlockMeta{
|
||||||
|
ULID: ulid.MustNew(ulid.Now(), nil),
|
||||||
|
MinTime: 0,
|
||||||
|
MaxTime: 1000,
|
||||||
|
Stats: tsdb.BlockStats{
|
||||||
|
NumSeries: 10,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
db := &fakeDB{
|
||||||
|
blockMetas: []tsdb.BlockMeta{blockMeta},
|
||||||
|
}
|
||||||
|
|
||||||
|
api := &API{
|
||||||
|
db: db,
|
||||||
|
}
|
||||||
|
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/api/v1/status/tsdb/blocks", nil)
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
|
||||||
|
result := api.serveTSDBBlocks(req)
|
||||||
|
|
||||||
|
json.NewEncoder(w).Encode(result.data)
|
||||||
|
|
||||||
|
resp := w.Result()
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
require.Equal(t, http.StatusOK, resp.StatusCode)
|
||||||
|
|
||||||
|
var resultData struct {
|
||||||
|
Blocks []tsdb.BlockMeta `json:"blocks"`
|
||||||
|
}
|
||||||
|
err := json.NewDecoder(resp.Body).Decode(&resultData)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Len(t, resultData.Blocks, 1)
|
||||||
|
require.Equal(t, blockMeta, resultData.Blocks[0])
|
||||||
|
}
|
||||||
|
|
||||||
func TestRespondError(t *testing.T) {
|
func TestRespondError(t *testing.T) {
|
||||||
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
||||||
api := API{}
|
api := API{}
|
||||||
|
@ -52,6 +52,10 @@ type dbAdapter struct {
|
|||||||
*tsdb.DB
|
*tsdb.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *dbAdapter) BlockMetas() ([]tsdb.BlockMeta, error) {
|
||||||
|
return a.DB.BlockMetas(), nil
|
||||||
|
}
|
||||||
|
|
||||||
func (a *dbAdapter) Stats(statsByLabelName string, limit int) (*tsdb.Stats, error) {
|
func (a *dbAdapter) Stats(statsByLabelName string, limit int) (*tsdb.Stats, error) {
|
||||||
return a.Head().Stats(statsByLabelName, limit), nil
|
return a.Head().Stats(statsByLabelName, limit), nil
|
||||||
}
|
}
|
||||||
@ -569,6 +573,7 @@ func TestAgentAPIEndPoints(t *testing.T) {
|
|||||||
"/query_range": {http.MethodGet, http.MethodPost},
|
"/query_range": {http.MethodGet, http.MethodPost},
|
||||||
"/query_exemplars": {http.MethodGet, http.MethodPost},
|
"/query_exemplars": {http.MethodGet, http.MethodPost},
|
||||||
"/status/tsdb": {http.MethodGet},
|
"/status/tsdb": {http.MethodGet},
|
||||||
|
"/status/tsdb/blocks": {http.MethodGet},
|
||||||
"/alerts": {http.MethodGet},
|
"/alerts": {http.MethodGet},
|
||||||
"/rules": {http.MethodGet},
|
"/rules": {http.MethodGet},
|
||||||
"/admin/tsdb/delete_series": {http.MethodPost, http.MethodPut},
|
"/admin/tsdb/delete_series": {http.MethodPost, http.MethodPut},
|
||||||
|
Loading…
Reference in New Issue
Block a user