diff --git a/docs/querying/api.md b/docs/querying/api.md index 15b2cb254..48794d98f 100644 --- a/docs/querying/api.md +++ b/docs/querying/api.md @@ -839,7 +839,6 @@ $ curl http://localhost:9090/api/v1/status/runtimeinfo "CWD": "/", "reloadConfigSuccess": true, "lastConfigTime": "2019-11-02T17:23:59+01:00", - "chunkCount": 873, "timeSeriesCount": 873, "corruptionCount": 0, "goroutineCount": 48, @@ -891,6 +890,11 @@ The following endpoint returns various cardinality statistics about the Promethe ``` GET /api/v1/status/tsdb ``` +- **headStats**: This provides the following data about the head block of the TSDB: + - **numSeries**: The number of series. + - **chunkCount**: The number of chunks. + - **minTime**: The current minimum timestamp in milliseconds. + - **maxTime**: The current maximum timestamp in milliseconds. - **seriesCountByMetricName:** This will provide a list of metrics names and their series count. - **labelValueCountByLabelName:** This will provide a list of the label names and their value count. - **memoryInBytesByLabelName** This will provide a list of the label names and memory used in bytes. Memory usage is calculated by adding the length of all values for a given label name. @@ -901,6 +905,12 @@ $ curl http://localhost:9090/api/v1/status/tsdb { "status": "success", "data": { + "headStats": { + "numSeries": 508, + "chunkCount": 937, + "minTime": 1591516800000, + "maxTime": 1598896800143, + }, "seriesCountByMetricName": [ { "name": "net_conntrack_dialer_conn_failed_total", diff --git a/web/api/v1/api.go b/web/api/v1/api.go index c5cdb3138..0c7a86d87 100644 --- a/web/api/v1/api.go +++ b/web/api/v1/api.go @@ -133,8 +133,6 @@ type RuntimeInfo struct { CWD string `json:"CWD"` ReloadConfigSuccess bool `json:"reloadConfigSuccess"` LastConfigTime time.Time `json:"lastConfigTime"` - ChunkCount int64 `json:"chunkCount"` - TimeSeriesCount int64 `json:"timeSeriesCount"` CorruptionCount int64 `json:"corruptionCount"` GoroutineCount int `json:"goroutineCount"` GOMAXPROCS int `json:"GOMAXPROCS"` @@ -194,6 +192,7 @@ type API struct { CORSOrigin *regexp.Regexp buildInfo *PrometheusVersion runtimeInfo func() (RuntimeInfo, error) + gatherer prometheus.Gatherer } func init() { @@ -222,6 +221,7 @@ func NewAPI( CORSOrigin *regexp.Regexp, runtimeInfo func() (RuntimeInfo, error), buildInfo *PrometheusVersion, + gatherer prometheus.Gatherer, ) *API { return &API{ QueryEngine: qe, @@ -245,6 +245,7 @@ func NewAPI( CORSOrigin: CORSOrigin, runtimeInfo: runtimeInfo, buildInfo: buildInfo, + gatherer: gatherer, } } @@ -1153,12 +1154,21 @@ type stat struct { Value uint64 `json:"value"` } +// HeadStats has information about the TSDB head. +type HeadStats struct { + NumSeries uint64 `json:"numSeries"` + ChunkCount int64 `json:"chunkCount"` + MinTime int64 `json:"minTime"` + MaxTime int64 `json:"maxTime"` +} + // tsdbStatus has information of cardinality statistics from postings. type tsdbStatus struct { - SeriesCountByMetricName []stat `json:"seriesCountByMetricName"` - LabelValueCountByLabelName []stat `json:"labelValueCountByLabelName"` - MemoryInBytesByLabelName []stat `json:"memoryInBytesByLabelName"` - SeriesCountByLabelValuePair []stat `json:"seriesCountByLabelValuePair"` + HeadStats HeadStats `json:"headStats"` + SeriesCountByMetricName []stat `json:"seriesCountByMetricName"` + LabelValueCountByLabelName []stat `json:"labelValueCountByLabelName"` + MemoryInBytesByLabelName []stat `json:"memoryInBytesByLabelName"` + SeriesCountByLabelValuePair []stat `json:"seriesCountByLabelValuePair"` } func convertStats(stats []index.Stat) []stat { @@ -1175,8 +1185,27 @@ func (api *API) serveTSDBStatus(*http.Request) apiFuncResult { if err != nil { return apiFuncResult{nil, &apiError{errorInternal, err}, nil, nil} } - + metrics, err := api.gatherer.Gather() + if err != nil { + return apiFuncResult{nil, &apiError{errorInternal, fmt.Errorf("error gathering runtime status: %s", err)}, nil, nil} + } + chunkCount := int64(math.NaN()) + for _, mF := range metrics { + if *mF.Name == "prometheus_tsdb_head_chunks" { + m := *mF.Metric[0] + if m.Gauge != nil { + chunkCount = int64(m.Gauge.GetValue()) + break + } + } + } return apiFuncResult{tsdbStatus{ + HeadStats: HeadStats{ + NumSeries: s.NumSeries, + ChunkCount: chunkCount, + MinTime: s.MinTime, + MaxTime: s.MaxTime, + }, SeriesCountByMetricName: convertStats(s.IndexPostingStats.CardinalityMetricsStats), LabelValueCountByLabelName: convertStats(s.IndexPostingStats.CardinalityLabelStats), MemoryInBytesByLabelName: convertStats(s.IndexPostingStats.LabelValueStats), diff --git a/web/api/v1/api_test.go b/web/api/v1/api_test.go index 8d2e309ed..19131da79 100644 --- a/web/api/v1/api_test.go +++ b/web/api/v1/api_test.go @@ -2733,7 +2733,7 @@ func TestTSDBStatus(t *testing.T) { } { tc := tc t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { - api := &API{db: tc.db} + api := &API{db: tc.db, gatherer: prometheus.DefaultGatherer} endpoint := tc.endpoint(api) req, err := http.NewRequest(tc.method, fmt.Sprintf("?%s", tc.values.Encode()), nil) if err != nil { diff --git a/web/ui/react-app/src/pages/status/Status.test.tsx b/web/ui/react-app/src/pages/status/Status.test.tsx index 72c442af2..be0ca6c42 100644 --- a/web/ui/react-app/src/pages/status/Status.test.tsx +++ b/web/ui/react-app/src/pages/status/Status.test.tsx @@ -11,8 +11,6 @@ describe('Status', () => { CWD: '/home/boyskila/Desktop/prometheus', reloadConfigSuccess: true, lastConfigTime: '2019-10-30T22:03:23+02:00', - chunkCount: 1383, - timeSeriesCount: 461, corruptionCount: 0, goroutineCount: 37, GOMAXPROCS: 4, diff --git a/web/ui/react-app/src/pages/status/Status.tsx b/web/ui/react-app/src/pages/status/Status.tsx index 94ff7272d..a6eb0da58 100644 --- a/web/ui/react-app/src/pages/status/Status.tsx +++ b/web/ui/react-app/src/pages/status/Status.tsx @@ -21,8 +21,6 @@ export const statusConfig: Record< customizeValue: (v: boolean) => (v ? 'Successful' : 'Unsuccessful'), }, lastConfigTime: { title: 'Last successful configuration reload' }, - chunkCount: { title: 'Head chunks' }, - timeSeriesCount: { title: 'Head time series' }, corruptionCount: { title: 'WAL corruptions' }, goroutineCount: { title: 'Goroutines' }, storageRetention: { title: 'Storage retention' }, diff --git a/web/ui/react-app/src/pages/tsdbStatus/TSDBStatus.test.tsx b/web/ui/react-app/src/pages/tsdbStatus/TSDBStatus.test.tsx index 828c3054d..56ecb2f2b 100644 --- a/web/ui/react-app/src/pages/tsdbStatus/TSDBStatus.test.tsx +++ b/web/ui/react-app/src/pages/tsdbStatus/TSDBStatus.test.tsx @@ -12,6 +12,12 @@ const fakeTSDBStatusResponse: { } = { status: 'success', data: { + headStats: { + numSeries: 508, + chunkCount: 937, + minTime: 1591516800000, + maxTime: 1598896800143, + }, labelValueCountByLabelName: [ { name: '__name__', @@ -51,22 +57,10 @@ describe('TSDB Stats', () => { describe('Table Data Validation', () => { it('Table Test', async () => { const tables = [ - { - data: fakeTSDBStatusResponse.data.labelValueCountByLabelName, - table_index: 0, - }, - { - data: fakeTSDBStatusResponse.data.seriesCountByMetricName, - table_index: 1, - }, - { - data: fakeTSDBStatusResponse.data.memoryInBytesByLabelName, - table_index: 2, - }, - { - data: fakeTSDBStatusResponse.data.seriesCountByLabelValuePair, - table_index: 3, - }, + fakeTSDBStatusResponse.data.labelValueCountByLabelName, + fakeTSDBStatusResponse.data.seriesCountByMetricName, + fakeTSDBStatusResponse.data.memoryInBytesByLabelName, + fakeTSDBStatusResponse.data.seriesCountByLabelValuePair, ]; const mock = fetchMock.mockResponse(JSON.stringify(fakeTSDBStatusResponse)); @@ -81,11 +75,22 @@ describe('TSDB Stats', () => { credentials: 'same-origin', }); + const headStats = page + .find(Table) + .at(0) + .find('tbody') + .find('td'); + ['508', '937', '2020-06-07T08:00:00.000Z (1591516800000)', '2020-08-31T18:00:00.143Z (1598896800143)'].forEach( + (value, i) => { + expect(headStats.at(i).text()).toEqual(value); + } + ); + for (let i = 0; i < tables.length; i++) { - const data = tables[i].data; + const data = tables[i]; const table = page .find(Table) - .at(tables[i].table_index) + .at(i + 1) .find('tbody'); const rows = table.find('tr'); for (let i = 0; i < data.length; i++) { diff --git a/web/ui/react-app/src/pages/tsdbStatus/TSDBStatus.tsx b/web/ui/react-app/src/pages/tsdbStatus/TSDBStatus.tsx index 111787f96..ff329148b 100644 --- a/web/ui/react-app/src/pages/tsdbStatus/TSDBStatus.tsx +++ b/web/ui/react-app/src/pages/tsdbStatus/TSDBStatus.tsx @@ -11,7 +11,15 @@ interface Stats { value: number; } +interface HeadStats { + numSeries: number; + chunkCount: number; + minTime: number; + maxTime: number; +} + export interface TSDBMap { + headStats: HeadStats; seriesCountByMetricName: Stats[]; labelValueCountByLabelName: Stats[]; memoryInBytesByLabelName: Stats[]; @@ -19,14 +27,42 @@ export interface TSDBMap { } export const TSDBStatusContent: FC = ({ + headStats, labelValueCountByLabelName, seriesCountByMetricName, memoryInBytesByLabelName, seriesCountByLabelValuePair, }) => { + const unixToTime = (unix: number): string => new Date(unix).toISOString(); + const { chunkCount, numSeries, minTime, maxTime } = headStats; + const stats = [ + { header: 'Number of Series', value: numSeries }, + { header: 'Number of Chunks', value: chunkCount }, + { header: 'Current Min Time', value: `${unixToTime(minTime)} (${minTime})` }, + { header: 'Current Max Time', value: `${unixToTime(maxTime)} (${maxTime})` }, + ]; return (

TSDB Status

+

Head Stats

+
+ + + + {stats.map(({ header }) => { + return ; + })} + + + + + {stats.map(({ header, value }) => { + return ; + })} + + +
{header}
{value}
+

Head Cardinality Stats

{[ { title: 'Top 10 label names with value count', stats: labelValueCountByLabelName }, diff --git a/web/web.go b/web/web.go index 056aff2ae..51ab23ad3 100644 --- a/web/web.go +++ b/web/web.go @@ -321,6 +321,7 @@ func New(logger log.Logger, o *Options) *Handler { h.options.CORSOrigin, h.runtimeInfo, h.versionInfo, + o.Gatherer, ) if o.RoutePrefix != "/" { @@ -796,10 +797,6 @@ func (h *Handler) runtimeInfo() (api_v1.RuntimeInfo, error) { } for _, mF := range metrics { switch *mF.Name { - case "prometheus_tsdb_head_chunks": - status.ChunkCount = int64(toFloat64(mF)) - case "prometheus_tsdb_head_series": - status.TimeSeriesCount = int64(toFloat64(mF)) case "prometheus_tsdb_wal_corruptions_total": status.CorruptionCount = int64(toFloat64(mF)) case "prometheus_config_last_reload_successful":