Skip to content

[BUG] Btrfs usage history is never shown: parameterized Flux query fails on InfluxDB OSS ("undefined identifier params"), same as #155 #1111

Closed

Description

@Unr3aL

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

  1. Omnibus image with the Btrfs collector enabled, at least one collection done.
  2. Open the Btrfs filesystem detail page: no "Usage History" chart.
  3. 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 [].)

  1. 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_key defaults to week in btrfs_get_details.go, the frontend passes none).
  • btrfs_filesystem points are written to the main bucket (15-day retention, web.influxdb.retention.daily) and are not part of the downsampling tasks, which only process smart and temp (scrutiny_repository_tasks.go). Longer ranges would query metrics_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=web

Environment

  • 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 on develop and beta as of 2026-11-05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions