API: expose lowest TSDB timestamp in /api/v1/status/tsdb

Add information about the lowest timestamp in the TSDB to /api/v1/status/tsdb.
This feature would be useful to have in Thanos so that the Thanos Sidecar can
adjust its advertised range so we can more precisely prune endpoints during fan-out.

Signed-off-by: Michael Hoffmann <mhoffm@posteo.de>
This commit is contained in:
Michael Hoffmann 2024-09-04 13:38:05 +02:00
parent d1ea6eb35d
commit 2dcaa1511c
No known key found for this signature in database
GPG key ID: E0DBDF3D046F608E
3 changed files with 53 additions and 6 deletions

View file

@ -1577,9 +1577,15 @@ type HeadStats struct {
MaxTime int64 `json:"maxTime"`
}
// TSDBStats has information about the TSDB.
type TSDBStats struct {
MinTime int64 `json:"minTime"`
}
// TSDBStatus has information of cardinality statistics from postings.
type TSDBStatus struct {
HeadStats HeadStats `json:"headStats"`
TSDBStats TSDBStats `json:"tsdbStats"`
SeriesCountByMetricName []TSDBStat `json:"seriesCountByMetricName"`
LabelValueCountByLabelName []TSDBStat `json:"labelValueCountByLabelName"`
MemoryInBytesByLabelName []TSDBStat `json:"memoryInBytesByLabelName"`
@ -1613,12 +1619,18 @@ func (api *API) serveTSDBStatus(r *http.Request) apiFuncResult {
return apiFuncResult{nil, &apiError{errorInternal, fmt.Errorf("error gathering runtime status: %w", err)}, nil, nil}
}
chunkCount := int64(math.NaN())
lowestTimestamp := int64(math.NaN())
for _, mF := range metrics {
if *mF.Name == "prometheus_tsdb_head_chunks" {
switch *mF.Name {
case "prometheus_tsdb_head_chunks":
m := mF.Metric[0]
if m.Gauge != nil {
chunkCount = int64(m.Gauge.GetValue())
break
}
case "prometheus_tsdb_lowest_timestamp":
m := mF.Metric[0]
if m.Gauge != nil {
lowestTimestamp = int64(m.Gauge.GetValue())
}
}
}
@ -1630,6 +1642,7 @@ func (api *API) serveTSDBStatus(r *http.Request) apiFuncResult {
MaxTime: s.MaxTime,
NumLabelPairs: s.IndexPostingStats.NumLabelPairs,
},
TSDBStats: TSDBStats{MinTime: lowestTimestamp},
SeriesCountByMetricName: TSDBStatsFromIndexStats(s.IndexPostingStats.CardinalityMetricsStats),
LabelValueCountByLabelName: TSDBStatsFromIndexStats(s.IndexPostingStats.CardinalityLabelStats),
MemoryInBytesByLabelName: TSDBStatsFromIndexStats(s.IndexPostingStats.LabelValueStats),

View file

@ -20,6 +20,9 @@ const fakeTSDBStatusResponse: {
minTime: 1591516800000,
maxTime: 1598896800143,
},
tsdbStats: {
minTime: 1591516800000,
},
labelValueCountByLabelName: [
{
name: '__name__',
@ -64,6 +67,9 @@ const fakeEmptyTSDBStatusResponse: {
minTime: 9223372036854776000,
maxTime: -9223372036854776000,
},
tsdbStats: {
minTime: 9223372036854776000,
},
labelValueCountByLabelName: [],
seriesCountByMetricName: [],
memoryInBytesByLabelName: [],
@ -84,6 +90,9 @@ const fakeInvalidTimestampTSDBStatusResponse: {
minTime: 9223372036854776000,
maxTime: -9223372036854776000,
},
tsdbStats: {
minTime: 9223372036854776000,
},
labelValueCountByLabelName: [],
seriesCountByMetricName: [],
memoryInBytesByLabelName: [],

View file

@ -19,8 +19,13 @@ interface HeadStats {
maxTime: number;
}
interface TSDBStats {
minTime: number;
}
export interface TSDBMap {
headStats: HeadStats;
tsdbStats: TSDBStats;
seriesCountByMetricName: Stats[];
labelValueCountByLabelName: Stats[];
memoryInBytesByLabelName: Stats[];
@ -45,29 +50,49 @@ export const TSDBStatusContent: FC<TSDBMap> = ({
}
};
const { chunkCount, numSeries, numLabelPairs, minTime, maxTime } = headStats;
const stats = [
const headStatsKV = [
{ header: 'Number of Series', value: numSeries },
{ header: 'Number of Chunks', value: chunkCount },
{ header: 'Number of Label Pairs', value: numLabelPairs },
{ header: 'Current Min Time', value: `${unixToTime(minTime)}` },
{ header: 'Current Max Time', value: `${unixToTime(maxTime)}` },
];
const tsdbStatsKV = [{ header: 'Current Min Time', value: `${unixToTime(minTime)}` }];
return (
<div>
<h2>TSDB Status</h2>
<h3 className="p-2">Head Stats</h3>
<h3 className="p-2">TSDB Stats</h3>
<div className="p-2">
<Table bordered size="sm" striped>
<thead>
<tr>
{stats.map(({ header }) => {
{tsdbStatsKV.map(({ header }) => {
return <th key={header}>{header}</th>;
})}
</tr>
</thead>
<tbody>
<tr>
{stats.map(({ header, value }) => {
{tsdbStatsKV.map(({ header, value }) => {
return <td key={header}>{value}</td>;
})}
</tr>
</tbody>
</Table>
</div>
<h3 className="p-2">Head Stats</h3>
<div className="p-2">
<Table bordered size="sm" striped>
<thead>
<tr>
{headStatsKV.map(({ header }) => {
return <th key={header}>{header}</th>;
})}
</tr>
</thead>
<tbody>
<tr>
{headStatsKV.map(({ header, value }) => {
return <td key={header}>{value}</td>;
})}
</tr>