Repository navigation
feat(analytics): record view time per dashboard path - #2218
alanpeixinho wants to merge 3 commits into
Conversation
* Report foreground time for each URL path. * End a visit when the path changes or the tab is hidden. * Ignore paths that match no dashboard route. * Keep this request out of the client, latency, and visitor metrics. Part of kernelci#2153 Assisted-by: Grok 4.7 Signed-off-by: Alan Peixinho <alan.peixinho@profusion.mobi>
* Use the dashboard app board from mon.kernelci.org. * Add a View time by route table. Assisted-by: Grok 4.7 Signed-off-by: Alan Peixinho <alan.peixinho@profusion.mobi>
felipebergamin
left a comment
There was a problem hiding this comment.
Comments are on the changed lines.
| if not body.is_valid() or not is_dashboard_path(body.validated_data["route"]): | ||
| return Response(status=HTTPStatus.BAD_REQUEST) | ||
| route = body.validated_data["route"] | ||
| get_metrics().route_visible_seconds.labels(route=route).observe( |
There was a problem hiding this comment.
route is the concrete path, this view sets authentication_classes = [], and settings has no DRF throttle. Each /build/<id> and /test/<id> adds histogram series, and the multiprocess files keep them until the process restarts. Observe the matched route pattern, for example /build/$buildId, instead of the path.
There was a problem hiding this comment.
The label keeps the tree name, branch, and hardware id, and stores a build, test, boot, issue, or commit id as its placeholder. /build/maestro:abc and /build/maestro:def share /build/$buildId.
Replacing every segment with the route pattern would drop which tree or board was open. The view still accepts the posted path and maps it before observing.
| }, | ||
| "enable": true, | ||
| "expr": "max by (version) (dashboard_build_info{job=\"dashboard_metrics\"}) unless max by (version) (dashboard_build_info{job=\"dashboard_metrics\"} offset 5m)", |
There was a problem hiding this comment.
Every query in this file filters job="dashboard_metrics". monitoring/prometheus.yml scrapes job_name: kernelci-backend, so this dashboard is empty on the stack in docs/monitoring.md. The previous file did not filter by job. Use a Grafana variable for the job, with the default set to the job this repo scrapes.
There was a problem hiding this comment.
Kept the filter hardcoded and set it to kernelci-backend, the job in monitoring/prometheus.yml.
docs/monitoring.md imports this file, and that scrape does not use dashboard_metrics, so the panels were empty. A $job variable would also serve mon.kernelci.org. Hardcoding the job this repo scrapes is the smaller change.
| `direct_or_internal`. The full referring URL is never stored. | ||
| - **Unique visitor estimates**: daily de-duplicated visit counts, in total and | ||
| per endpoint. | ||
| - **View time**: while you use the dashboard in a browser, the page reports |
There was a problem hiding this comment.
A path that only one person opens, such as /build/<id>, plus the scrape timestamp, identifies that visit and when it happened. Line 21 says metrics are never per-user records or per-user timestamps. Recording the route pattern instead of the path fixes that. This bullet should describe the pattern, not the concrete path.
There was a problem hiding this comment.
A build, test, boot, issue, or commit id is stored as its placeholder, so /build/<id> is no longer its own series. The bullet says that.
Tree name, branch, and hardware id stay in the label, so those pages can still be ranked. The bullet names those too, for example /tree/mainline/master.
* Keep tree, branch, and hardware in the view-time label and collapse object ids * Point the Grafana board at kernelci-backend * Describe the stored route in PRIVACY.md and the monitoring docs Assisted-by: Grok 4.7 Part of kernelci#2153. Signed-off-by: Alan Peixinho <alan.peixinho@profusion.mobi>
What it is
Records how long a dashboard URL stays in the foreground. A visit ends when the path changes or the tab is hidden. Showing the tab again starts a new visit. Changing only the query string does not end the visit.
The browser sends one POST to
/api/analytics/visible/when the visit ends. The JSON body isrouteandtime_ms.routeis the URL path, such as/tree/mainline/master. The handler dividestime_msby 1000 and observesdashboard_route_visible_seconds. Buckets are 1, 5, 15, 30, 60, 120, 300, 600, 1800, and +Inf. A stay longer than 30 minutes lands in +Inf.A path that matches no dashboard route, or a
time_msoutside 0 to 86400000, returns 400 and is not recorded. The/$not-found route is rejected because it would match every path. This POST does not increment the client-request counter, the latency histogram, or the unique-visitor count. A visit that never closes is missing.routeis the full path, so each commit, build, or test id is its own series. Bounding that label is left for a follow-up.monitoring/dashboard.jsonis now the dashboard app board from mon.kernelci.org, with a View time by route table added.Part of #2153
How to test
cd dashboard && pnpm exec vitest run src/utils/visibleTime.test.tscd backend && poetry run pytest kernelCI_app/tests/unitTests/views/visibleTimeView_test.py kernelCI_app/tests/unitTests/middleware/backendRequestMetricsMiddleware_test.pyWith the app running and
PROMETHEUS_METRICS_ENABLED=true:/tree/mainline/master, wait a few seconds, then open another path or hide the tab. The network panel shows one POST to/api/analytics/visible/with that path andtime_ms. Changing only the search string sends nothing. A stay under one second still sends.curl -s localhost:8001/metrics/ | grep dashboard_route_visible_secondsshows a count for that path. A POST with{"route":"/nope","time_ms":1000}returns 400 and adds no series.monitoring/dashboard.json. The View time by route table lists the path over the selected range, including a path visited once.Made with Cursor