Repository navigation
Description
Describe the bug
The Btrfs filesystem detail page never shows its "Usage History" chart. The history query fails on every request, and the failure is only logged as a warning.
Root cause
GetBtrfsMetricsHistory() (webapp/backend/pkg/database/scrutiny_repository_btrfs.go) uses a parameterized Flux query:
|> filter(fn: (r) => r["filesystem_uuid"] == params.uuid)
...
result, err := sr.influxQueryApi.QueryWithParams(ctx, queryStr, map[string]interface{}{"uuid": uuid})Flux query parameters (params) are only supported by InfluxDB Cloud, not by InfluxDB OSS, which the omnibus image ships (2.9.1). The influxdb-client-go PR that added QueryWithParams states "Supported by InfluxDB Cloud only now" (influxdata/influxdb-client-go#308). So the query fails with undefined identifier params.
btrfs_get_details.go only logs the error and returns metrics_history: null, and the frontend renders the chart only when the history is non-empty, so the chart silently never appears.
This is the same error class as #155 (SMART device details failed with undefined identifier params, fixed in January). The Btrfs query was added later with the same construct.
GetZFSPoolMetricsHistory() (scrutiny_repository_zfs.go) uses the same construct (params.guid with QueryWithParams) and is most likely affected the same way. I have no ZFS host to verify it.
Both are still present on develop and beta.
Reproduction
- Omnibus image with the Btrfs collector enabled, at least one collection done.
- Open the Btrfs filesystem detail page: no "Usage History" chart.
- API response:
$ curl -s http://localhost:8080/api/btrfs/filesystem/<uuid>/details | grep -o '"metrics_history":[^,]*'
"metrics_history":null}
(null means the query failed; an empty result would be [].)
- Log:
level=warning msg="Could not get Btrfs metrics history failed to query Btrfs metrics: invalid: error @5:48-5:54: undefined identifier params" type=web
Expected behavior
The Btrfs detail page shows the usage history from the stored btrfs_filesystem metrics.
Possible fix (sketch, not compiled): drop QueryWithParams and put the identifier directly into the query string. That is safe here because the identifier is already restricted to a harmless character set: the handlers validate it (btrfs_get_details.go → validation.ValidateUUID(), zfs_get_details.go → validation.ValidateGUID()), and GetMdadmMetricsHistory() already uses exactly this pattern. Validating again in the repository function keeps it safe for any future caller. strconv.Quote() produces a valid Flux string literal, as the temperature queries already do (scrutiny_repository_temperature.go).
scrutiny_repository_btrfs.go, GetBtrfsMetricsHistory():
func (sr *scrutinyRepository) GetBtrfsMetricsHistory(ctx context.Context, uuid string, durationKey string) ([]measurements.BtrfsMetrics, error) {
+ // Flux query parameters (params.*) are InfluxDB Cloud only and fail on OSS
+ // ("undefined identifier params", see #155). Validate and inline instead.
+ if err := validation.ValidateUUID(uuid); err != nil {
+ return nil, err
+ }
bucketName := sr.lookupBucketName(durationKey)
duration := sr.lookupDuration(durationKey)
queryStr := fmt.Sprintf(`
from(bucket: "%s")
|> range(start: %s, stop: %s)
|> filter(fn: (r) => r["_measurement"] == "btrfs_filesystem")
- |> filter(fn: (r) => r["filesystem_uuid"] == params.uuid)
+ |> filter(fn: (r) => r["filesystem_uuid"] == %s)
|> aggregateWindow(every: 1h, fn: last, createEmpty: false)
|> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value")
|> sort(columns: ["_time"], desc: false)
- `, bucketName, duration[0], duration[1])
- result, err := sr.influxQueryApi.QueryWithParams(ctx, queryStr, map[string]interface{}{"uuid": uuid})
+ `, bucketName, duration[0], duration[1], strconv.Quote(uuid))
+ result, err := sr.influxQueryApi.Query(ctx, queryStr)scrutiny_repository_zfs.go, GetZFSPoolMetricsHistory(), the same change with the GUID:
func (sr *scrutinyRepository) GetZFSPoolMetricsHistory(ctx context.Context, guid string, durationKey string) ([]measurements.ZFSPoolMetrics, error) {
+ // See GetBtrfsMetricsHistory: params.* is InfluxDB Cloud only.
+ if err := validation.ValidateGUID(guid); err != nil {
+ return nil, err
+ }
// Map duration key to actual duration and bucket
bucketName := sr.lookupBucketName(durationKey)
duration := sr.lookupDuration(durationKey)
- // Use parameterized query to prevent Flux injection
+ // guid is validated above (ValidateGUID allows only digits or 0x + hex), so inlining is safe
queryStr := fmt.Sprintf(`
from(bucket: "%s")
|> range(start: %s, stop: %s)
|> filter(fn: (r) => r["_measurement"] == "zfs_pool")
- |> filter(fn: (r) => r["pool_guid"] == params.guid)
+ |> filter(fn: (r) => r["pool_guid"] == %s)
|> aggregateWindow(every: 1h, fn: last, createEmpty: false)
|> pivot(rowKey:["_time"], columnKey: ["_field"], valueColumn: "_value")
|> sort(columns: ["_time"], desc: false)
- `, bucketName, duration[0], duration[1])
-
- params := map[string]interface{}{
- "guid": guid,
- }
-
- result, err := sr.influxQueryApi.QueryWithParams(ctx, queryStr, params)
+ `, bucketName, duration[0], duration[1], strconv.Quote(guid))
+
+ result, err := sr.influxQueryApi.Query(ctx, queryStr)Both files already import validation; strconv needs to be added to the imports.
Follow-up (once the query works): usage history is currently limited to 7 days
Even with the query fixed, the chart can only ever show the last week, which isn't very telling for volume usage, where the long-term trend is what matters:
- The detail page always requests the default range (
duration_keydefaults toweekinbtrfs_get_details.go, the frontend passes none). btrfs_filesystempoints are written to the main bucket (15-day retention,web.influxdb.retention.daily) and are not part of the downsampling tasks, which only processsmartandtemp(scrutiny_repository_tasks.go). Longer ranges would querymetrics_weekly/_monthly, which contain no Btrfs data.
For usage, downsampling fits well (e.g. last or mean of statfs_used / statfs_available per window), unlike state values such as degraded/missing devices, where short events would be averaged away. So a minimal approach could be: downsample only the capacity fields of btrfs_filesystem and offer a range selector on the detail page, like the temperature chart does. The same would apply to ZFS pool capacity.
Screenshots
No response
Log Files
level=warning msg="Could not get Btrfs metrics history failed to query Btrfs metrics: invalid: error @5:48-5:54: undefined identifier params" type=webEnvironment
- Scrutiny:
ghcr.io/starosdev/scrutiny:develop-omnibus(code identical to v1.79.1 for this path), bundled InfluxDB 2.9.1 - Host: Synology DS920+ (x86_64), DSM 7.4.1, kernel 4.4.302+, Docker via Synology Container Manager
- Btrfs collector enabled (single-device volume on
/dev/mapper/cachedev_0) - Code reference:
v1.79.1; also present ondevelopandbetaas of 2026-11-05
Metadata
Metadata
Assignees
Labels
Projects
- StatusShow more project fieldsBug Backlog
Activity
fix(database): inline validated ids in Btrfs and ZFS history queries …
chore(release): 1.80.0