mirror of
https://github.com/prometheus/prometheus.git
synced 2025-03-05 20:59:13 -08:00
React UI: Implement missing TSDB head stats section (#7876)
* React UI: Implement missing TSDB head stats section Signed-off-by: Dustin Hooten <dhooten@splunk.com> * Add break Signed-off-by: Dustin Hooten <dhooten@splunk.com>
This commit is contained in:
parent
19c190b406
commit
916dbd4c8a
|
@ -839,7 +839,6 @@ $ curl http://localhost:9090/api/v1/status/runtimeinfo
|
||||||
"CWD": "/",
|
"CWD": "/",
|
||||||
"reloadConfigSuccess": true,
|
"reloadConfigSuccess": true,
|
||||||
"lastConfigTime": "2019-11-02T17:23:59+01:00",
|
"lastConfigTime": "2019-11-02T17:23:59+01:00",
|
||||||
"chunkCount": 873,
|
|
||||||
"timeSeriesCount": 873,
|
"timeSeriesCount": 873,
|
||||||
"corruptionCount": 0,
|
"corruptionCount": 0,
|
||||||
"goroutineCount": 48,
|
"goroutineCount": 48,
|
||||||
|
@ -891,6 +890,11 @@ The following endpoint returns various cardinality statistics about the Promethe
|
||||||
```
|
```
|
||||||
GET /api/v1/status/tsdb
|
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.
|
- **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.
|
- **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.
|
- **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",
|
"status": "success",
|
||||||
"data": {
|
"data": {
|
||||||
|
"headStats": {
|
||||||
|
"numSeries": 508,
|
||||||
|
"chunkCount": 937,
|
||||||
|
"minTime": 1591516800000,
|
||||||
|
"maxTime": 1598896800143,
|
||||||
|
},
|
||||||
"seriesCountByMetricName": [
|
"seriesCountByMetricName": [
|
||||||
{
|
{
|
||||||
"name": "net_conntrack_dialer_conn_failed_total",
|
"name": "net_conntrack_dialer_conn_failed_total",
|
||||||
|
|
|
@ -133,8 +133,6 @@ type RuntimeInfo struct {
|
||||||
CWD string `json:"CWD"`
|
CWD string `json:"CWD"`
|
||||||
ReloadConfigSuccess bool `json:"reloadConfigSuccess"`
|
ReloadConfigSuccess bool `json:"reloadConfigSuccess"`
|
||||||
LastConfigTime time.Time `json:"lastConfigTime"`
|
LastConfigTime time.Time `json:"lastConfigTime"`
|
||||||
ChunkCount int64 `json:"chunkCount"`
|
|
||||||
TimeSeriesCount int64 `json:"timeSeriesCount"`
|
|
||||||
CorruptionCount int64 `json:"corruptionCount"`
|
CorruptionCount int64 `json:"corruptionCount"`
|
||||||
GoroutineCount int `json:"goroutineCount"`
|
GoroutineCount int `json:"goroutineCount"`
|
||||||
GOMAXPROCS int `json:"GOMAXPROCS"`
|
GOMAXPROCS int `json:"GOMAXPROCS"`
|
||||||
|
@ -194,6 +192,7 @@ type API struct {
|
||||||
CORSOrigin *regexp.Regexp
|
CORSOrigin *regexp.Regexp
|
||||||
buildInfo *PrometheusVersion
|
buildInfo *PrometheusVersion
|
||||||
runtimeInfo func() (RuntimeInfo, error)
|
runtimeInfo func() (RuntimeInfo, error)
|
||||||
|
gatherer prometheus.Gatherer
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -222,6 +221,7 @@ func NewAPI(
|
||||||
CORSOrigin *regexp.Regexp,
|
CORSOrigin *regexp.Regexp,
|
||||||
runtimeInfo func() (RuntimeInfo, error),
|
runtimeInfo func() (RuntimeInfo, error),
|
||||||
buildInfo *PrometheusVersion,
|
buildInfo *PrometheusVersion,
|
||||||
|
gatherer prometheus.Gatherer,
|
||||||
) *API {
|
) *API {
|
||||||
return &API{
|
return &API{
|
||||||
QueryEngine: qe,
|
QueryEngine: qe,
|
||||||
|
@ -245,6 +245,7 @@ func NewAPI(
|
||||||
CORSOrigin: CORSOrigin,
|
CORSOrigin: CORSOrigin,
|
||||||
runtimeInfo: runtimeInfo,
|
runtimeInfo: runtimeInfo,
|
||||||
buildInfo: buildInfo,
|
buildInfo: buildInfo,
|
||||||
|
gatherer: gatherer,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1153,12 +1154,21 @@ type stat struct {
|
||||||
Value uint64 `json:"value"`
|
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.
|
// tsdbStatus has information of cardinality statistics from postings.
|
||||||
type tsdbStatus struct {
|
type tsdbStatus struct {
|
||||||
SeriesCountByMetricName []stat `json:"seriesCountByMetricName"`
|
HeadStats HeadStats `json:"headStats"`
|
||||||
LabelValueCountByLabelName []stat `json:"labelValueCountByLabelName"`
|
SeriesCountByMetricName []stat `json:"seriesCountByMetricName"`
|
||||||
MemoryInBytesByLabelName []stat `json:"memoryInBytesByLabelName"`
|
LabelValueCountByLabelName []stat `json:"labelValueCountByLabelName"`
|
||||||
SeriesCountByLabelValuePair []stat `json:"seriesCountByLabelValuePair"`
|
MemoryInBytesByLabelName []stat `json:"memoryInBytesByLabelName"`
|
||||||
|
SeriesCountByLabelValuePair []stat `json:"seriesCountByLabelValuePair"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func convertStats(stats []index.Stat) []stat {
|
func convertStats(stats []index.Stat) []stat {
|
||||||
|
@ -1175,8 +1185,27 @@ func (api *API) serveTSDBStatus(*http.Request) apiFuncResult {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return apiFuncResult{nil, &apiError{errorInternal, err}, nil, 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{
|
return apiFuncResult{tsdbStatus{
|
||||||
|
HeadStats: HeadStats{
|
||||||
|
NumSeries: s.NumSeries,
|
||||||
|
ChunkCount: chunkCount,
|
||||||
|
MinTime: s.MinTime,
|
||||||
|
MaxTime: s.MaxTime,
|
||||||
|
},
|
||||||
SeriesCountByMetricName: convertStats(s.IndexPostingStats.CardinalityMetricsStats),
|
SeriesCountByMetricName: convertStats(s.IndexPostingStats.CardinalityMetricsStats),
|
||||||
LabelValueCountByLabelName: convertStats(s.IndexPostingStats.CardinalityLabelStats),
|
LabelValueCountByLabelName: convertStats(s.IndexPostingStats.CardinalityLabelStats),
|
||||||
MemoryInBytesByLabelName: convertStats(s.IndexPostingStats.LabelValueStats),
|
MemoryInBytesByLabelName: convertStats(s.IndexPostingStats.LabelValueStats),
|
||||||
|
|
|
@ -2733,7 +2733,7 @@ func TestTSDBStatus(t *testing.T) {
|
||||||
} {
|
} {
|
||||||
tc := tc
|
tc := tc
|
||||||
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
|
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)
|
endpoint := tc.endpoint(api)
|
||||||
req, err := http.NewRequest(tc.method, fmt.Sprintf("?%s", tc.values.Encode()), nil)
|
req, err := http.NewRequest(tc.method, fmt.Sprintf("?%s", tc.values.Encode()), nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -11,8 +11,6 @@ describe('Status', () => {
|
||||||
CWD: '/home/boyskila/Desktop/prometheus',
|
CWD: '/home/boyskila/Desktop/prometheus',
|
||||||
reloadConfigSuccess: true,
|
reloadConfigSuccess: true,
|
||||||
lastConfigTime: '2019-10-30T22:03:23+02:00',
|
lastConfigTime: '2019-10-30T22:03:23+02:00',
|
||||||
chunkCount: 1383,
|
|
||||||
timeSeriesCount: 461,
|
|
||||||
corruptionCount: 0,
|
corruptionCount: 0,
|
||||||
goroutineCount: 37,
|
goroutineCount: 37,
|
||||||
GOMAXPROCS: 4,
|
GOMAXPROCS: 4,
|
||||||
|
|
|
@ -21,8 +21,6 @@ export const statusConfig: Record<
|
||||||
customizeValue: (v: boolean) => (v ? 'Successful' : 'Unsuccessful'),
|
customizeValue: (v: boolean) => (v ? 'Successful' : 'Unsuccessful'),
|
||||||
},
|
},
|
||||||
lastConfigTime: { title: 'Last successful configuration reload' },
|
lastConfigTime: { title: 'Last successful configuration reload' },
|
||||||
chunkCount: { title: 'Head chunks' },
|
|
||||||
timeSeriesCount: { title: 'Head time series' },
|
|
||||||
corruptionCount: { title: 'WAL corruptions' },
|
corruptionCount: { title: 'WAL corruptions' },
|
||||||
goroutineCount: { title: 'Goroutines' },
|
goroutineCount: { title: 'Goroutines' },
|
||||||
storageRetention: { title: 'Storage retention' },
|
storageRetention: { title: 'Storage retention' },
|
||||||
|
|
|
@ -12,6 +12,12 @@ const fakeTSDBStatusResponse: {
|
||||||
} = {
|
} = {
|
||||||
status: 'success',
|
status: 'success',
|
||||||
data: {
|
data: {
|
||||||
|
headStats: {
|
||||||
|
numSeries: 508,
|
||||||
|
chunkCount: 937,
|
||||||
|
minTime: 1591516800000,
|
||||||
|
maxTime: 1598896800143,
|
||||||
|
},
|
||||||
labelValueCountByLabelName: [
|
labelValueCountByLabelName: [
|
||||||
{
|
{
|
||||||
name: '__name__',
|
name: '__name__',
|
||||||
|
@ -51,22 +57,10 @@ describe('TSDB Stats', () => {
|
||||||
describe('Table Data Validation', () => {
|
describe('Table Data Validation', () => {
|
||||||
it('Table Test', async () => {
|
it('Table Test', async () => {
|
||||||
const tables = [
|
const tables = [
|
||||||
{
|
fakeTSDBStatusResponse.data.labelValueCountByLabelName,
|
||||||
data: fakeTSDBStatusResponse.data.labelValueCountByLabelName,
|
fakeTSDBStatusResponse.data.seriesCountByMetricName,
|
||||||
table_index: 0,
|
fakeTSDBStatusResponse.data.memoryInBytesByLabelName,
|
||||||
},
|
fakeTSDBStatusResponse.data.seriesCountByLabelValuePair,
|
||||||
{
|
|
||||||
data: fakeTSDBStatusResponse.data.seriesCountByMetricName,
|
|
||||||
table_index: 1,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
data: fakeTSDBStatusResponse.data.memoryInBytesByLabelName,
|
|
||||||
table_index: 2,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
data: fakeTSDBStatusResponse.data.seriesCountByLabelValuePair,
|
|
||||||
table_index: 3,
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
const mock = fetchMock.mockResponse(JSON.stringify(fakeTSDBStatusResponse));
|
const mock = fetchMock.mockResponse(JSON.stringify(fakeTSDBStatusResponse));
|
||||||
|
@ -81,11 +75,22 @@ describe('TSDB Stats', () => {
|
||||||
credentials: 'same-origin',
|
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++) {
|
for (let i = 0; i < tables.length; i++) {
|
||||||
const data = tables[i].data;
|
const data = tables[i];
|
||||||
const table = page
|
const table = page
|
||||||
.find(Table)
|
.find(Table)
|
||||||
.at(tables[i].table_index)
|
.at(i + 1)
|
||||||
.find('tbody');
|
.find('tbody');
|
||||||
const rows = table.find('tr');
|
const rows = table.find('tr');
|
||||||
for (let i = 0; i < data.length; i++) {
|
for (let i = 0; i < data.length; i++) {
|
||||||
|
|
|
@ -11,7 +11,15 @@ interface Stats {
|
||||||
value: number;
|
value: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface HeadStats {
|
||||||
|
numSeries: number;
|
||||||
|
chunkCount: number;
|
||||||
|
minTime: number;
|
||||||
|
maxTime: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface TSDBMap {
|
export interface TSDBMap {
|
||||||
|
headStats: HeadStats;
|
||||||
seriesCountByMetricName: Stats[];
|
seriesCountByMetricName: Stats[];
|
||||||
labelValueCountByLabelName: Stats[];
|
labelValueCountByLabelName: Stats[];
|
||||||
memoryInBytesByLabelName: Stats[];
|
memoryInBytesByLabelName: Stats[];
|
||||||
|
@ -19,14 +27,42 @@ export interface TSDBMap {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TSDBStatusContent: FC<TSDBMap> = ({
|
export const TSDBStatusContent: FC<TSDBMap> = ({
|
||||||
|
headStats,
|
||||||
labelValueCountByLabelName,
|
labelValueCountByLabelName,
|
||||||
seriesCountByMetricName,
|
seriesCountByMetricName,
|
||||||
memoryInBytesByLabelName,
|
memoryInBytesByLabelName,
|
||||||
seriesCountByLabelValuePair,
|
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 (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h2>TSDB Status</h2>
|
<h2>TSDB Status</h2>
|
||||||
|
<h3 className="p-2">Head Stats</h3>
|
||||||
|
<div className="p-2">
|
||||||
|
<Table bordered size="sm" striped>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
{stats.map(({ header }) => {
|
||||||
|
return <th key={header}>{header}</th>;
|
||||||
|
})}
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
{stats.map(({ header, value }) => {
|
||||||
|
return <td key={header}>{value}</td>;
|
||||||
|
})}
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
<h3 className="p-2">Head Cardinality Stats</h3>
|
<h3 className="p-2">Head Cardinality Stats</h3>
|
||||||
{[
|
{[
|
||||||
{ title: 'Top 10 label names with value count', stats: labelValueCountByLabelName },
|
{ title: 'Top 10 label names with value count', stats: labelValueCountByLabelName },
|
||||||
|
|
|
@ -321,6 +321,7 @@ func New(logger log.Logger, o *Options) *Handler {
|
||||||
h.options.CORSOrigin,
|
h.options.CORSOrigin,
|
||||||
h.runtimeInfo,
|
h.runtimeInfo,
|
||||||
h.versionInfo,
|
h.versionInfo,
|
||||||
|
o.Gatherer,
|
||||||
)
|
)
|
||||||
|
|
||||||
if o.RoutePrefix != "/" {
|
if o.RoutePrefix != "/" {
|
||||||
|
@ -796,10 +797,6 @@ func (h *Handler) runtimeInfo() (api_v1.RuntimeInfo, error) {
|
||||||
}
|
}
|
||||||
for _, mF := range metrics {
|
for _, mF := range metrics {
|
||||||
switch *mF.Name {
|
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":
|
case "prometheus_tsdb_wal_corruptions_total":
|
||||||
status.CorruptionCount = int64(toFloat64(mF))
|
status.CorruptionCount = int64(toFloat64(mF))
|
||||||
case "prometheus_config_last_reload_successful":
|
case "prometheus_config_last_reload_successful":
|
||||||
|
|
Loading…
Reference in a new issue