Bug description
When a Trace Funnel has a step transition that zero traces complete (e.g. a step's (service_name, span_name) matches no spans in the window, or no trace satisfies the temporal ordering t2 > t1), the funnel analytics endpoints return HTTP 500 instead of reporting 0% conversion:
POST /api/v1/trace-funnels/{funnel_id}/analytics/overview → 500 with body app.ApiResponse.Data: []*v3.Row: v3.Row.Data: unsupported value: NaN
POST /api/v1/trace-funnels/{funnel_id}/analytics/steps/overview → 500 with the same error
"Nothing reached this step" is arguably the most important answer a funnel can give, and it's exactly the case that errors out.
Root cause
The generated ClickHouse aggregates produce NaN on empty sets, and Go's encoding/json cannot marshal NaN, so the handler 500s. In pkg/modules/tracefunnel/clickhouse_queries.go:
-
BuildFunnelOverviewQuery — the conversion division is already guarded:
round(if(total_s1_spans > 0, total_sN_spans * 100.0 / total_s1_spans, 0), 2) AS conversion_rate
…but avg_duration and latency are not:
avgIf((toUnixTimestamp64Nano(tN_time) - toUnixTimestamp64Nano(t1_time))/1e6, <full-funnel condition>) AS avg_duration,
quantileIf(0.99)(...) AS latency
When zero traces satisfy the full-funnel condition, ClickHouse returns NaN for both → JSON marshal fails → 500. (So a funnel whose conversion correctly computes as 0 still 500s because of the latency fields.)
-
BuildFunnelStepOverviewQuery — has the same unguarded avgIf/quantileIf, plus its conversion division lacks the guard that BuildFunnelOverviewQuery already has:
round(total_sEnd_spans * 100.0 / total_sStart_spans, 2) AS conversion_rate
If the start step matches zero traces this is 0 * 100.0 / 0 → NaN as well.
How to reproduce
On a self-hosted v0.132.2 with any traces present (times: timestamp in ms, analytics windows in ns):
# 1. Create a funnel
curl -X POST http://localhost:8080/api/v1/trace-funnels/new \
-H "Authorization: Bearer $JWT" -H 'Content-Type: application/json' \
-d '{"funnel_name":"nan-repro","timestamp":1784219000000}'
# 2. Configure steps where step 2 matches an existing span but step 3 matches nothing
# (or nothing that occurs after step 2 within the same trace)
curl -X PUT http://localhost:8080/api/v1/trace-funnels/steps/update \
-H "Authorization: Bearer $JWT" -H 'Content-Type: application/json' \
-d '{"funnel_id":"<id>","timestamp":1784219000000,"steps":[
{"step_order":1,"service_name":"my-svc","span_name":"existing-span-a","filters":{"items":[],"op":"AND"},"latency_pointer":"start","has_errors":false},
{"step_order":2,"service_name":"my-svc","span_name":"existing-span-b","filters":{"items":[],"op":"AND"},"latency_pointer":"start","has_errors":false},
{"step_order":3,"service_name":"my-svc","span_name":"does-not-exist","filters":{"items":[],"op":"AND"},"latency_pointer":"start","has_errors":false}]}'
# 3. Overview → HTTP 500
curl -X POST http://localhost:8080/api/v1/trace-funnels/<id>/analytics/overview \
-H "Authorization: Bearer $JWT" -H 'Content-Type: application/json' \
-d '{"start_time":1781627000000000000,"end_time":1784219000000000000}'
# → app.ApiResponse.Data: []*v3.Row: v3.Row.Data: unsupported value: NaN
# 4. Step transition into the empty step → HTTP 500
curl -X POST http://localhost:8080/api/v1/trace-funnels/<id>/analytics/steps/overview \
-H "Authorization: Bearer $JWT" -H 'Content-Type: application/json' \
-d '{"start_time":1781627000000000000,"end_time":1784219000000000000,"step_start":2,"step_end":3}'
Control check confirming the trigger: with the same funnel re-pointed so step 3 matches spans (/analytics/steps reporting total_s3_spans: 5 instead of 0), both endpoints return 200 with sane numbers (e.g. conversion_rate: 22.73). The 500 appears exactly when a step count drops to zero.
Expected behavior
200 with conversion_rate: 0 (and avg_duration/latency as 0 or null) — a funnel step that nothing reaches is a valid, meaningful result, not an internal error.
Suggested fix
Wrap the empty-set aggregates in both builders, e.g. ifNaN(avgIf(...), 0) / ifNaN(quantileIf(...)(...), 0), and add the missing zero-guard to BuildFunnelStepOverviewQuery's conversion division, mirroring the guard that already exists in BuildFunnelOverviewQuery. Happy to send a PR for this.
Version information
- SigNoz version: v0.132.2 (
/api/v1/version → {"version":"v0.132.2","ee":"Y","setupCompleted":true}), self-hosted via Foundry (docker compose flavor)
- OS: Windows 11, Docker Desktop (WSL2 backend)
Thank you for your bug report – we love squashing them!
Bug description
When a Trace Funnel has a step transition that zero traces complete (e.g. a step's
(service_name, span_name)matches no spans in the window, or no trace satisfies the temporal orderingt2 > t1), the funnel analytics endpoints return HTTP 500 instead of reporting 0% conversion:POST /api/v1/trace-funnels/{funnel_id}/analytics/overview→500with bodyapp.ApiResponse.Data: []*v3.Row: v3.Row.Data: unsupported value: NaNPOST /api/v1/trace-funnels/{funnel_id}/analytics/steps/overview→500with the same error"Nothing reached this step" is arguably the most important answer a funnel can give, and it's exactly the case that errors out.
Root cause
The generated ClickHouse aggregates produce
NaNon empty sets, and Go'sencoding/jsoncannot marshalNaN, so the handler 500s. Inpkg/modules/tracefunnel/clickhouse_queries.go:BuildFunnelOverviewQuery— the conversion division is already guarded:…but
avg_durationandlatencyare not:When zero traces satisfy the full-funnel condition, ClickHouse returns
NaNfor both → JSON marshal fails → 500. (So a funnel whose conversion correctly computes as 0 still 500s because of the latency fields.)BuildFunnelStepOverviewQuery— has the same unguardedavgIf/quantileIf, plus its conversion division lacks the guard thatBuildFunnelOverviewQueryalready has:If the start step matches zero traces this is
0 * 100.0 / 0→NaNas well.How to reproduce
On a self-hosted v0.132.2 with any traces present (times:
timestampin ms, analytics windows in ns):Control check confirming the trigger: with the same funnel re-pointed so step 3 matches spans (
/analytics/stepsreportingtotal_s3_spans: 5instead of0), both endpoints return 200 with sane numbers (e.g.conversion_rate: 22.73). The 500 appears exactly when a step count drops to zero.Expected behavior
200withconversion_rate: 0(andavg_duration/latencyas0ornull) — a funnel step that nothing reaches is a valid, meaningful result, not an internal error.Suggested fix
Wrap the empty-set aggregates in both builders, e.g.
ifNaN(avgIf(...), 0)/ifNaN(quantileIf(...)(...), 0), and add the missing zero-guard toBuildFunnelStepOverviewQuery's conversion division, mirroring the guard that already exists inBuildFunnelOverviewQuery. Happy to send a PR for this.Version information
/api/v1/version→{"version":"v0.132.2","ee":"Y","setupCompleted":true}), self-hosted via Foundry (docker compose flavor)Thank you for your bug report – we love squashing them!