Skip to content

feat(analytics): record API latency by endpoint and client - #2156

Merged
alanpeixinho merged 6 commits into
kernelci:mainfrom
profusion:feat/analytics-latency-by-client
Oct 8, 2026
Merged

alanpeixinho merged 6 commits into
kernelci:mainfrom
profusion:feat/analytics-latency-by-client

Conversation

@alanpeixinho

@alanpeixinho alanpeixinho commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

What it is

  • Time each finished /api/ request on dashboard_backend_request_latency_seconds, labeled by endpoint and client.
  • Stop observing those requests on the Django view-latency histogram. /admin/ and /health/ stay on it. Django status and exception counters stay as they are.
  • Point Total Calls by Endpoint at dashboard_backend_requests_by_client_total, summed by endpoint and client. Point Average Response Time by Endpoint at the histogram sum divided by count, and add p50 and p95 from its buckets. Endpoint Performance Summary uses the counter for calls and the histogram for total time and average.
  • Add a 4xx and 5xx by Endpoint panel on that same counter, summed by endpoint and status_class. A 200 does not appear.

The existing Client dropdown filters those panels. New series start at deploy. API views already on the Django histogram stop receiving samples.

Built on #2154. Merge that first.

Closes #2149
Closes #2150

How to test

From backend/:

poetry run pytest -n 0 -m unit kernelCI_app/tests/unitTests/middleware/backendRequestMetricsMiddleware_test.py -q
  1. docker compose -f docker-compose.monitoring.yml up -d
  2. Start the backend with PROMETHEUS_METRICS_ENABLED=true and metrics on port 8001. runserver needs --noreload.
  3. Send a few requests:
curl -s -o /dev/null -D - -H 'User-Agent: kci-dev/0.1.11 (Linux)' http://localhost:8000/api/schema/
curl -s -o /dev/null -D - -H 'User-Agent: curl/8.5.0' http://localhost:8000/api/schema/
curl -s -o /dev/null -D - -A 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36' http://localhost:8000/api/schema/
curl -s -o /dev/null -D - -A 'Mozilla/5.0' http://localhost:8000/admin/
curl -s -o /dev/null -D - http://localhost:8000/api/hardware/
  1. On http://localhost:8001/metrics/ confirm client="kci-dev", client="script", and client="dashboard" on both dashboard_backend_requests_by_client_total and dashboard_backend_request_latency_seconds_count, for endpoint schema. django_http_requests_latency_seconds_by_view_method_count must not have view="schema". It must have admin:index or admin:login. The hardware request is endpoint="hardware", status_class="4xx".
  2. In Grafana at http://localhost:3000, sign in as admin / admin. The Prometheus datasource URL is http://prometheus:9090 and its uid must be prometheus. Import monitoring/dashboard.json. Set the range to the last 15 minutes.
  3. With Client set to All clients, Total Calls shows schema split by dashboard, kci-dev, and script. Average Response Time shows avg, p50, and p95 for those series after Prometheus has scraped twice. Client set to kci-dev leaves only that client. Summary Mode set to All Time shows the totals.
  4. 4xx and 5xx by Endpoint shows hardware - 4xx. It does not show schema.

@alanpeixinho
alanpeixinho force-pushed the feat/analytics-latency-by-client branch 2 times, most recently from 49ffcb5 to c9ba9d2 Compare October 2, 2026 18:45
Comment thread backend/kernelCI_app/middleware/backendRequestMetricsMiddleware.py Outdated
Comment thread backend/kernelCI_app/middleware/backendRequestMetricsMiddleware.py Outdated
Comment thread backend/kernelCI_app/middleware/backendRequestMetricsMiddleware.py
* Time /api/ requests on dashboard_backend_request_latency_seconds by endpoint and client
* Keep /admin/ and /health/ on the Django view-latency histogram
* Point total calls, average, p50, and p95 at those client series

Built on kernelci#2154.
Closes kernelci#2149

Signed-off-by: Alan Peixinho <alan.peixinho@profusion.mobi>
* Add a panel summed by endpoint and status_class, limited to 4xx and 5xx

Part of kernelci#2150

Signed-off-by: Alan Peixinho <alan.peixinho@profusion.mobi>
The latency tests replaced the process-wide Prometheus histogram and left
it replaced, so the health-skip test saw no /api/ observation once both
suites shared a worker.

Signed-off-by: Alan Peixinho <alan.peixinho@profusion.mobi>
Ruff I001 failed pre-commit because prometheus_client sat after the first-party import.

Assisted-by: Cursor
Signed-off-by: Alan Peixinho <alan.peixinho@profusion.mobi>
GET and POST to the same endpoint were recorded as one series.

Signed-off-by: Alan Peixinho <alan.peixinho@profusion.mobi>
@alanpeixinho
alanpeixinho force-pushed the feat/analytics-latency-by-client branch from 0dc78b3 to 410a7a7 Compare October 7, 2026 18:47
Comment thread monitoring/dashboard.json Outdated
},
"editorMode": "code",
"expr": "(sum(django_http_requests_latency_seconds_by_view_method_count{view!=\"<unnamed view>\"}) by (view, method) and on() label_replace(vector(1), \"mode\", \"$summary_mode\", \"\", \"\")==label_replace(vector(1), \"mode\", \"all\", \"\", \"\") or round(sum(increase(django_http_requests_latency_seconds_by_view_method_count{view!=\"<unnamed view>\"}[$__range:])) by (view, method)) and on() label_replace(vector(1), \"mode\", \"$summary_mode\", \"\", \"\")==label_replace(vector(1), \"mode\", \"range\", \"\", \"\"))",
"expr": "(sum by (endpoint, client) (dashboard_backend_requests_by_client_total{client=~\"$client\"}) and on() label_replace(vector(1), \"mode\", \"$summary_mode\", \"\", \"\")==label_replace(vector(1), \"mode\", \"all\", \"\", \"\") or round(sum by (endpoint, client) (increase(dashboard_backend_requests_by_client_total{client=~\"$client\"}[$__range:]))) and on() label_replace(vector(1), \"mode\", \"$summary_mode\", \"\", \"\")==label_replace(vector(1), \"mode\", \"range\", \"\", \"\"))",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This counter has been scraped since #2154. The histogram sum below starts when this deploys, and the table divides that time by these calls. All Time average stays low, because earlier calls are in this count and the histogram has no time for them. A fresh local Prometheus will not show that.

Could this column use dashboard_backend_request_latency_seconds_count, with the same grouping and the same all/range switch? Total Calls can keep this counter.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

#2154 is on main, but it is not in production, so dashboard_backend_requests_by_client_total is not being scraped there yet. If we merge this PR, both should reach production together.

Comment thread monitoring/dashboard.json Outdated
},
"editorMode": "code",
"expr": "(sum(django_http_requests_latency_seconds_by_view_method_sum{view!=\"<unnamed view>\"}) by (view, method) and on() label_replace(vector(1), \"mode\", \"$summary_mode\", \"\", \"\")==label_replace(vector(1), \"mode\", \"all\", \"\", \"\") or sum(increase(django_http_requests_latency_seconds_by_view_method_sum{view!=\"<unnamed view>\"}[$__range:])) by (view, method) and on() label_replace(vector(1), \"mode\", \"$summary_mode\", \"\", \"\")==label_replace(vector(1), \"mode\", \"range\", \"\", \"\"))",
"expr": "(sum by (endpoint, client) (dashboard_backend_request_latency_seconds_sum{client=~\"$client\"}) and on() label_replace(vector(1), \"mode\", \"$summary_mode\", \"\", \"\")==label_replace(vector(1), \"mode\", \"all\", \"\", \"\") or sum by (endpoint, client) (increase(dashboard_backend_request_latency_seconds_sum{client=~\"$client\"}[$__range:])) and on() label_replace(vector(1), \"mode\", \"$summary_mode\", \"\", \"\")==label_replace(vector(1), \"mode\", \"range\", \"\", \"\"))",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This groups only by endpoint and client, so GET and POST share one average. Total Calls above does the same. The latency chart keeps method. Add method to both sums if the table should keep them apart.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes. Total Calls and both summary sums now group by endpoint, method, and client, same as the latency chart. The summary table groups on Method as well, so Grafana does not add GET and POST back into one row.

endpoint=labels["endpoint"],
method=labels["method"],
client=labels["client"],
).observe(time.perf_counter() - started)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This sample includes the labeling and the counter update above. The visitor cache write below stays outside it. Save the elapsed time right after get_response returns, and pass that into observe, so the labeling stays out of the histogram.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

nice catch

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Changed to make time measurement only on the response generation.

Stop the histogram sample when the response returns, and group the
dashboard sums by method so GET and POST stay apart.

Assisted-by: Cursor
Signed-off-by: Alan Peixinho <alan.peixinho@profusion.mobi>

@felipebergamin felipebergamin left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

approving. but please take a look on my comment about the docs/monitoring.md file

Comment thread docs/monitoring.md

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

just a small update on this doc.

it still describes the dashboard from before the method split. The feature list dropped Method, and the Client section says Total Calls is summed by endpoint and client only. The queries also group by method. The same feature list is not mentioning 4xx and 5xx by Endpoint.

@alanpeixinho
alanpeixinho added this pull request to the merge queue Oct 8, 2026
Merged via the queue into kernelci:main with commit 70439c9 Oct 8, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4xx and 5xx by endpoint Requests and latency by endpoint, split by client

2 participants