console: ensure main table is used when required even when no data

The user will get empty data, it may be better than an empty table. Add
more tests as well.

Fix #1935
This commit is contained in:
Vincent Bernat
2025-09-04 14:17:23 +02:00
parent 04ce99602f
commit ec3c6e8ffa
3 changed files with 135 additions and 21 deletions

View File

@@ -218,7 +218,7 @@ func (c *Component) computeTableAndInterval(input inputContext) (string, time.Du
// Select table // Select table
targetIntervalForTableSelection := targetInterval targetIntervalForTableSelection := targetInterval
if input.MainTableRequired { if input.MainTableRequired {
targetIntervalForTableSelection = time.Second return "flows", time.Second, targetInterval
} }
startForTableSelection := input.Start startForTableSelection := input.Start
if input.StartForTableSelection != nil { if input.StartForTableSelection != nil {
@@ -269,7 +269,7 @@ func (c *Component) getBestTable(start time.Time, targetInterval time.Duration)
}) })
// If possible, use the first resolution before the target interval // If possible, use the first resolution before the target interval
for len(candidates) > 1 { for len(candidates) > 1 {
if c.flowsTables[candidates[1]].Resolution < targetInterval { if c.flowsTables[candidates[1]].Resolution <= targetInterval {
candidates = candidates[1:] candidates = candidates[1:]
} else { } else {
break break

View File

@@ -312,7 +312,7 @@ func TestComputeBestTableAndInterval(t *testing.T) {
Expected tableIntervalOutput Expected tableIntervalOutput
}{ }{
{ {
Description: "simple query without additional tables", Description: "only flows table",
Context: inputContext{ Context: inputContext{
Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC), Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC),
End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC), End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
@@ -320,7 +320,7 @@ func TestComputeBestTableAndInterval(t *testing.T) {
}, },
Expected: tableIntervalOutput{Table: "flows", Interval: 1}, Expected: tableIntervalOutput{Table: "flows", Interval: 1},
}, { }, {
Description: "query with main table", Description: "only flows table, require main",
Context: inputContext{ Context: inputContext{
Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC), Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC),
End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC), End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
@@ -329,28 +329,16 @@ func TestComputeBestTableAndInterval(t *testing.T) {
}, },
Expected: tableIntervalOutput{Table: "flows", Interval: 1}, Expected: tableIntervalOutput{Table: "flows", Interval: 1},
}, { }, {
Description: "only flows table available", Description: "only flows table available, out of range",
Tables: []flowsTable{{"flows", 0, time.Date(2022, 3, 10, 15, 45, 10, 0, time.UTC)}}, Tables: []flowsTable{{"flows", 0, time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC)}},
Context: inputContext{ Context: inputContext{
Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC), Start: time.Date(2022, 4, 8, 15, 45, 10, 0, time.UTC),
End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC), End: time.Date(2022, 4, 9, 15, 45, 10, 0, time.UTC),
Points: 86400, Points: 86400,
}, },
Expected: tableIntervalOutput{Table: "flows", Interval: 1}, Expected: tableIntervalOutput{Table: "flows", Interval: 1},
}, { }, {
Description: "select flows table out of range", Description: "consolidated table with better resolution",
Tables: []flowsTable{
{"flows", 0, time.Date(2022, 4, 10, 16, 45, 10, 0, time.UTC)},
{"flows_1m0s", time.Minute, time.Date(2022, 4, 10, 17, 45, 10, 0, time.UTC)},
},
Context: inputContext{
Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC),
End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
Points: 720, // 2-minute resolution,
},
Expected: tableIntervalOutput{Table: "flows", Interval: 1},
}, {
Description: "select consolidated table with better resolution",
Tables: []flowsTable{ Tables: []flowsTable{
{"flows", 0, time.Date(2022, 3, 10, 22, 45, 10, 0, time.UTC)}, {"flows", 0, time.Date(2022, 3, 10, 22, 45, 10, 0, time.UTC)},
{"flows_5m0s", 5 * time.Minute, time.Date(2022, 4, 2, 22, 45, 10, 0, time.UTC)}, {"flows_5m0s", 5 * time.Minute, time.Date(2022, 4, 2, 22, 45, 10, 0, time.UTC)},
@@ -362,6 +350,131 @@ func TestComputeBestTableAndInterval(t *testing.T) {
Points: 720, // 2-minute resolution, Points: 720, // 2-minute resolution,
}, },
Expected: tableIntervalOutput{Table: "flows_1m0s", Interval: 60}, Expected: tableIntervalOutput{Table: "flows_1m0s", Interval: 60},
}, {
Description: "consolidated table available, but main required",
Tables: []flowsTable{
{"flows", 0, time.Date(2022, 3, 10, 22, 45, 10, 0, time.UTC)},
{"flows_5m0s", 5 * time.Minute, time.Date(2022, 4, 2, 22, 45, 10, 0, time.UTC)},
{"flows_1m0s", time.Minute, time.Date(2022, 4, 2, 22, 45, 10, 0, time.UTC)},
},
Context: inputContext{
Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC),
End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
Points: 720, // 2-minute resolution,
MainTableRequired: true,
},
Expected: tableIntervalOutput{Table: "flows", Interval: 1},
}, {
Description: "consolidated table available, but out of range",
Tables: []flowsTable{
{"flows", 0, time.Date(2022, 3, 10, 22, 45, 10, 0, time.UTC)},
{"flows_5m0s", 5 * time.Minute, time.Date(2022, 4, 20, 22, 45, 10, 0, time.UTC)},
{"flows_1m0s", time.Minute, time.Date(2022, 4, 20, 22, 45, 10, 0, time.UTC)},
},
Context: inputContext{
Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC),
End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
Points: 720, // 2-minute resolution,
},
Expected: tableIntervalOutput{Table: "flows", Interval: 1},
}, {
Description: "consolidated table available, main table required, out of range",
Tables: []flowsTable{
{"flows", 0, time.Date(2022, 4, 20, 22, 45, 10, 0, time.UTC)},
{"flows_5m0s", 5 * time.Minute, time.Date(2022, 4, 2, 22, 45, 10, 0, time.UTC)},
{"flows_1m0s", time.Minute, time.Date(2022, 4, 2, 22, 45, 10, 0, time.UTC)},
},
Context: inputContext{
Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC),
End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
Points: 720, // 2-minute resolution,
MainTableRequired: true,
},
Expected: tableIntervalOutput{Table: "flows", Interval: 1},
}, {
Description: "empty flows tables list",
Tables: []flowsTable{},
Context: inputContext{
Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC),
End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
Points: 86400,
},
Expected: tableIntervalOutput{Table: "flows", Interval: 1},
}, {
Description: "target interval smaller than 1 second",
Tables: []flowsTable{
{"flows", 0, time.Date(2022, 4, 10, 12, 45, 10, 0, time.UTC)},
},
Context: inputContext{
Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC),
End: time.Date(2022, 4, 10, 15, 45, 20, 0, time.UTC), // 10 seconds with many points
Points: 100000,
},
Expected: tableIntervalOutput{Table: "flows", Interval: 1},
}, {
Description: "multiple tables with same resolution, choose oldest data",
Tables: []flowsTable{
{"flows", 0, time.Date(2022, 4, 10, 12, 45, 10, 0, time.UTC)},
{"flows_1m0s_a", time.Minute, time.Date(2022, 4, 9, 12, 45, 10, 0, time.UTC)},
{"flows_1m0s_b", time.Minute, time.Date(2022, 4, 8, 12, 45, 10, 0, time.UTC)},
},
Context: inputContext{
Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC),
End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
Points: 720, // 2-minute resolution
},
Expected: tableIntervalOutput{Table: "flows_1m0s_b", Interval: 60},
}, {
Description: "choose best resolution below target interval",
Tables: []flowsTable{
{"flows", 0, time.Date(2022, 4, 8, 12, 45, 10, 0, time.UTC)},
{"flows_10s", 10 * time.Second, time.Date(2022, 4, 9, 12, 45, 10, 0, time.UTC)},
{"flows_30s", 30 * time.Second, time.Date(2022, 4, 9, 12, 45, 10, 0, time.UTC)},
{"flows_2m0s", 2 * time.Minute, time.Date(2022, 4, 9, 12, 45, 10, 0, time.UTC)},
},
Context: inputContext{
Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC),
End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
Points: 1440, // 1-minute target interval
},
Expected: tableIntervalOutput{Table: "flows_30s", Interval: 30},
}, {
Description: "all tables out of range, choose table with oldest data",
Tables: []flowsTable{
{"flows", 0, time.Date(2022, 4, 15, 12, 45, 10, 0, time.UTC)},
{"flows_1m0s", time.Minute, time.Date(2022, 4, 14, 12, 45, 10, 0, time.UTC)},
{"flows_5m0s", 5 * time.Minute, time.Date(2022, 4, 12, 12, 45, 10, 0, time.UTC)},
},
Context: inputContext{
Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC),
End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
Points: 720,
},
Expected: tableIntervalOutput{Table: "flows_5m0s", Interval: 300},
}, {
Description: "resolution exactly matches target interval",
Tables: []flowsTable{
{"flows", 0, time.Date(2022, 4, 8, 12, 45, 10, 0, time.UTC)},
{"flows_2m0s", 2 * time.Minute, time.Date(2022, 4, 9, 12, 45, 10, 0, time.UTC)},
},
Context: inputContext{
Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC),
End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
Points: 720, // Exactly 2-minute interval
},
Expected: tableIntervalOutput{Table: "flows_2m0s", Interval: 120},
}, {
Description: "sub-second resolution gets clamped to 1 second",
Tables: []flowsTable{
{"flows", 0, time.Date(2022, 4, 8, 12, 45, 10, 0, time.UTC)},
{"flows_100ms", 100 * time.Millisecond, time.Date(2022, 4, 9, 12, 45, 10, 0, time.UTC)},
},
Context: inputContext{
Start: time.Date(2022, 4, 10, 15, 45, 10, 0, time.UTC),
End: time.Date(2022, 4, 11, 15, 45, 10, 0, time.UTC),
Points: 8640000, // Very high resolution request
},
Expected: tableIntervalOutput{Table: "flows_100ms", Interval: 1}, // Clamped to 1 second
}, },
} }

View File

@@ -13,6 +13,7 @@ identified with a specific icon:
## Unreleased ## Unreleased
- 🩹 *console*: display missing images in documentation - 🩹 *console*: display missing images in documentation
- 🩹 *console*: ensure main table is used when required even when there is no data
- 🩹 *docker*: fix broken `/metrics` endpoint for inlet - 🩹 *docker*: fix broken `/metrics` endpoint for inlet
- 🌱 *build*: accept building with a not up-to-date toolchain - 🌱 *build*: accept building with a not up-to-date toolchain
- 🌱 *docker*: update ClickHouse to 25.8 (not mandatory) - 🌱 *docker*: update ClickHouse to 25.8 (not mandatory)