mirror of
https://github.com/prometheus/prometheus.git
synced 2024-11-09 23:24:05 -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": "/",
|
||||
"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",
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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' },
|
||||
|
|
|
@ -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++) {
|
||||
|
|
|
@ -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<TSDBMap> = ({
|
||||
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 (
|
||||
<div>
|
||||
<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>
|
||||
{[
|
||||
{ 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.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":
|
||||
|
|
Loading…
Reference in a new issue