Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 51 additions & 4 deletions grafana/dashboards/watchdog_metrics_dashboard.json
Original file line number Diff line number Diff line change
Expand Up @@ -171,32 +171,79 @@
"datasource": {
"name": "Prometheus"
},
"description": "Metrics: gtfs_bundle_days_until_earliest_expiration, gtfs_bundle_days_until_latest_expiration\nQuestion answered: \"Are GTFS bundles approaching expiration?\"",
"description": "Metrics: gtfs_bundle_days_until_earliest_expiration, gtfs_bundle_days_until_latest_expiration\nQuestion answered: \"Are GTFS bundles approaching expiration?\"\n\nVisualization: Bar Gauge\nRationale: This metric is a countdown. Rendering it as a horizontal bar gauge allows operators to stack multiple feeds side-by-side. The bars act as a visual timer that shifts from green to warning red as the days until expiration drop below the critical threshold.",
"fieldConfig": {
"defaults": {
"color": {
"mode": "thresholds"
},
"max": 7776000,
"min": 0,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "red",
"value": null
},
{
"color": "yellow",
"value": 604800
},
{
"color": "green",
"value": 2592000
}
]
},
"unit": "dtdurations"
},
Comment on lines +199 to +200

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
import json
from pathlib import Path

dashboard = json.loads(
    Path("grafana/dashboards/watchdog_metrics_dashboard.json").read_text()
)
panel = next(panel for panel in dashboard["panels"] if panel.get("id") == 201)
defaults = panel["fieldConfig"]["defaults"]

assert defaults["unit"] == "dtdurations"
assert defaults.get("decimals") == 1
PY

Repository: OneBusAway/watchdog

Length of output: 246


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== dashboard context =="
python3 - <<'PY'
import json
from pathlib import Path
dashboard = json.loads(Path("grafana/dashboards/watchdog_metrics_dashboard.json").read_text())
for panel in dashboard["panels"]:
    if panel.get("id") == 201:
        defaults = panel["fieldConfig"]["defaults"]
        print(json.dumps({"id": panel.get("id"), "title": panel.get("title"), "type": panel.get("type")}, indent=2))
        print(json.dumps(defaults, indent=2))
PY

echo
echo "== occurrences around dashboards and duration formatter usage =="
rg -n '"dtdurations"|valueFormats|decimals|8 weeks|week|days' grafana/dashboards/watchdog_metrics_dashboard.json

Repository: OneBusAway/watchdog

Length of output: 1408


🌐 Web query:

Grafana dtdurations value format decimals behavior display next duration component

💡 Result:

In Grafana, the dtdurations unit (Duration (s)) is a specialized formatter designed to represent numeric values (interpreted as seconds) as human-readable durations [1][2][3]. The behavior of decimals and the display of duration components is governed by Grafana's internal display processor, which dynamically handles unit scaling based on the magnitude of the value [4][5][6]. Key behaviors include: 1. Automatic Scaling and Precision: By default, Grafana automatically determines the scale (e.g., seconds, milliseconds) and truncates decimals based on the magnitude of the data [6]. When the Decimals option is left empty, Grafana attempts to automatically truncate excess precision [6]. If a specific decimal count is provided, it dictates the precision used in the formatted output [4]. 2. Decimals Behavior: Setting the Decimals option to a fixed number (e.g., 0) can influence how Grafana renders values [7][5]. Users have reported that forcing 0 decimals can sometimes cause issues with Y-axis granularity or result in NaN values when data ranges are very tight, as the formatter may struggle to represent small variations within a single unit interval (e.g., sub-second differences) [7][5]. 3. Display of Duration Components: The dtdurations unit generally scales automatically to the most appropriate unit [6]. If you require a specific format that explicitly displays multiple components (like days, hours, minutes, and seconds), you may need to use other dedicated units, such as: - dthms (Duration (hh:mm:ss)) [1][2] - dtdhms (Duration (d hh:mm:ss)) [1][2] Because dtdurations is a dynamic formatter, it does not always offer granular control over exactly which "next" duration component is displayed if the value is very small; it will shift the display to milliseconds or other smaller units as necessary to maintain accuracy [4][5]. If you encounter rendering issues (such as NaN or loss of data), it is often related to how the automatic scaling interacts with fixed decimal constraints on the axis [7][5].

Citations:


🌐 Web query:

Grafana dtdurations formatter days weeks decimals components source Grafana v13.0.2

💡 Result:

In Grafana v13.0.2, the dtdurations formatter (labeled as "duration (s)" in the UI) is designed to automatically scale duration values based on their magnitude [1][2]. It is not currently possible to manually force this formatter to use specific components like days or weeks while excluding others, nor is there a setting to explicitly lock the units to a specific scale while allowing decimal values [3]. Technical details for the dtdurations formatter: 1. Functionality: The formatter is defined by the toDurationInSeconds function within Grafana's codebase [1]. It applies automatic unit scaling based on the magnitude of the input value [2]. 2. Scaling Logic: The formatter automatically transitions between units based on thresholds [2]: - Less than 1 µs: Uses ns - Less than 1 ms: Uses µs - Less than 1 s: Uses ms - Less than 60 s: Uses s - Less than 3600 s: Uses minutes (min) - Less than 86400 s: Uses hours (hour) - Less than 604800 s: Uses days (day) - Less than 31536000 s: Uses weeks (week) - Above this threshold: Uses years [2]. 3. Decimals: The formatter accepts a DecimalCount parameter which controls the precision of the output [2]. When the value reaches the threshold for a specific unit (e.g., days), the value is divided by the relevant constant (e.g., 86400 for days) and displayed with the specified number of decimals [2]. Because this behavior is hardcoded in the data transformation logic, users seeking to force specific units (such as only days) are generally advised to either perform the unit conversion directly in their data source query or use a custom expression to divide the input value by the desired unit interval (e.g., dividing seconds by 86400) and applying a custom unit label [3].

Citations:


Set dtdurations to show the day-level countdown.

Panel 201 uses "unit": "dtdurations" without "decimals", so Grafana renders the largest duration component only. Add "decimals": 1 so values such as several weeks and a day show the day component instead of only weeks.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@grafana/dashboards/watchdog_metrics_dashboard.json` around lines 199 - 200,
Update the panel 201 field using the "dtdurations" unit to include "decimals":
1, ensuring Grafana displays the day-level countdown component while preserving
the existing unit configuration.

Source: MCP tools

"overrides": []
},
"gridPos": {
"h": 8,
"w": 12,
"x": 0,
"y": 10
},
"id": 201,
"options": {
"displayMode": "gradient",
"maxVizHeight": 300,
"minVizHeight": 16,
"minVizWidth": 75,
"namePlacement": "auto",
"orientation": "horizontal",
"reduceOptions": {
"calcs": ["lastNotNull"],
"fields": "",
"values": false
},
"showUnfilled": true,
"sizing": "auto",
"valueMode": "color"
},
"pluginVersion": "13.0.2",
"targets": [
{
"expr": "gtfs_bundle_days_until_earliest_expiration{server_id=~\"$server_id\"}",
"expr": "gtfs_bundle_days_until_earliest_expiration{server_id=~\"$server_id\"} * 86400",
"legendFormat": "Earliest Expiration (Server {{server_id}})",
"refId": "A",
"datasource": {
"name": "Prometheus"
}
},
{
"expr": "gtfs_bundle_days_until_latest_expiration{server_id=~\"$server_id\"}",
"expr": "gtfs_bundle_days_until_latest_expiration{server_id=~\"$server_id\"} * 86400",
"legendFormat": "Latest Expiration (Server {{server_id}})",
"refId": "B",
"datasource": {
"name": "Prometheus"
}
}
],
"title": "2.1 Feed Expiration",
"type": "timeseries"
"type": "bargauge"
},
{
"datasource": {
Expand Down
Loading