RousselTMLEARNING

Observability

Conversion funnels in RUM: measuring where users drop off

Building and reading a conversion funnel with RUM data: steps, rates, order, pitfalls, and implementation in Dynatrace, Grafana Faro, Datadog and New Relic.

1. What is a conversion funnel? #

The concept and its vocabulary.

The principle #

A conversion funnel follows the steps of an expected journey, for example product page, cart, payment, order, and counts how many sessions reach each one. It answers a simple question: where do users drop off?

RUM (Real User Monitoring) is an ideal source: it sees real sessions, in the real browser, with their errors and slowness. A RUM funnel does not only say how many drop off, it helps understand why.

Four-step funnel from 10,000 to 2,500 sessions, with the biggest loss between cart and payment

The vocabulary #

TermDefinition
StepAn observable event: a page view, a click, a business event
Step rateSessions at the step ÷ sessions at the previous step
Overall conversionSessions at the last step ÷ sessions at the first
Drop-off1 − step rate: the share lost between two steps
UnitWhat you count: session (most common in RUM) or user
WindowThe period in which steps must happen

2. Building your funnel properly #

The choices that change the numbers.

Step order #

Three sessions compared: only an ordered funnel excludes the session paying before seeing the product

Many tools do not check order: a session that performed all steps, in any order, is counted. Dynatrace's documentation says so for USQL FUNNEL, New Relic's for funnel(). We checked it on Loki with the part 3 query: a session paying before seeing the product is counted to the end.

For a linear journey (cart then payment), the gap is small. For steps that can repeat or be reversed, prefer a tool that enforces order, such as Datadog funnels.

Best practices #

  • 3 to 6 steps, each tied to a user decision. Beyond that, the funnel becomes unreadable.
  • Stable event names: add_to_cart everywhere, never addToCart on one page and cart_add elsewhere.
  • Count distinct sessions, not events: a user reloading the page must not inflate a step.
  • Segment: browser, device, country, app version. A drop concentrated on mobile Safari points to a bug, not to pricing.
  • Cross with technical data: JavaScript errors, load times and Core Web Vitals of the pages where users drop off.
  • Protect data: never put an e-mail, name or exact per-customer amount in event attributes.

3. Grafana Faro #

Faro events and LogQL queries.

Instrumenting steps #

Faro has no funnel screen: you build it with events, sent with pushEvent and stored in Loki with the kind=event label.

// One step = one named event, always with the same names
faro.api.pushEvent('vue_produit', { produit: 'casque-x' });
faro.api.pushEvent('ajout_panier', { produit: 'casque-x', quantite: '1' });
faro.api.pushEvent('paiement', { moyen: 'carte' });
// Two identical events in a row are deduplicated by Faro:
faro.api.pushEvent('ajout_panier', { produit: 'casque-x' }, undefined, { skipDedupe: true });

Counting each step in LogQL #

Distinct sessions that viewed a product:

count(
  sum by (session_id) (count_over_time({app_name="boutique", kind="event"} | logfmt | event_name="vue_produit" [24h]))
)

Sessions that viewed a product and added to cart: the and operator only keeps sessions present in both results.

count(
    sum by (session_id) (count_over_time({app_name="boutique", kind="event"} | logfmt | event_name="vue_produit"  [24h]))
  and
    sum by (session_id) (count_over_time({app_name="boutique", kind="event"} | logfmt | event_name="ajout_panier" [24h]))
)

Third step:

count(
    sum by (session_id) (count_over_time({app_name="boutique", kind="event"} | logfmt | event_name="vue_produit"  [24h]))
  and
    sum by (session_id) (count_over_time({app_name="boutique", kind="event"} | logfmt | event_name="ajout_panier" [24h]))
  and
    sum by (session_id) (count_over_time({app_name="boutique", kind="event"} | logfmt | event_name="paiement"     [24h]))
)
QueryTest result
Product4
Product and cart3
Product, cart and payment2 (including one session that paid first)
The simple count trap. sum by (event_name) (count_over_time(...)) gave 4 cart additions for 4 product views: one session added to cart without the product page. Count sessions that went through the previous steps, not events.

In Grafana, put each query in a Bar gauge or Stat panel, one query per step, to get a readable funnel. These queries were tested on Loki 3.7.9.

4. Dynatrace #

Conversion goals, USQL and DQL.

RUM Classic: USQL FUNNEL #

With RUM Classic, first declare conversion goals on the application, then use the USQL FUNNEL function:

SELECT FUNNEL(
  useraction.name = "AppStart (easyTravel)" AS "Open",
  useraction.name = "searchJourney" AS "Search",
  useraction.name = "bookJourney" AS "Book",
  useraction.matchingConversionGoals = "Payment" AS "Paid")
FROM usersession
WHERE stringProperties.memberstatus = "GOLD"
  • 10 conditions at most.
  • No GROUP BY, ORDER BY or LIMIT.
  • Order is not guaranteed: the documentation states nothing ensures the first action precedes the second.

Combined with Session Replay, the funnel lets you replay sessions that drop off at a step.

New RUM experience: DQL #

On the Grail platform, RUM data lives in the user.events table, queried with DQL. The documentation shows how to count sessions per page:

fetch user.events
| filter characteristics.has_navigation
| summarize sessions = countDistinct(dt.rum.session.id), by: {view = view.url.path}
| sort sessions desc

On this model, a funnel is built in two aggregations: first one row per session with the number of visits per step, then the count of sessions that went through each step and the previous ones.

fetch user.events, from: now() - 7d
| filter characteristics.has_navigation
| summarize {
    produit  = countIf(view.url.path == "/produit"),
    panier   = countIf(view.url.path == "/panier"),
    paiement = countIf(view.url.path == "/paiement")
  }, by: {dt.rum.session.id}
| summarize {
    `1. Produit`  = countIf(produit > 0),
    `2. Panier`   = countIf(produit > 0 and panier > 0),
    `3. Paiement` = countIf(produit > 0 and panier > 0 and paiement > 0)
  }
Validate in your environment. Fields come from the new RUM experience documentation, still evolving; we could not run this query. Adapt page paths to your application.

5. Datadog and New Relic #

Two other approaches.

Datadog #

Datadog offers a dedicated screen, Digital Experience > Product Analytics > Funnels: you pick a starting view or action, then add steps with the + button. Actions happening between two steps do not affect the rates. Funnel queries can be reused in a RUM monitor to alert on a conversion rate drop.

New Relic #

FROM PageView
SELECT funnel(session,
  WHERE pageUrl LIKE '%browse%' AS 'Browse',
  WHERE pageUrl LIKE '%cart%' AS 'Cart',
  WHERE pageUrl LIKE '%checkout%' AS 'Checkout')
SINCE 1 week AGO
  • One grouping attribute per query (session here).
  • 10 steps at most; approximate results beyond 256 distinct values.
  • Order is not enforced: the first step is always 100%.

Summary #

ToolHowStep order
Grafana FaropushEvent + LogQL in GrafanaNot checked
Dynatrace (Classic)Conversion goals + USQL FUNNELNot guaranteed
Dynatrace (new RUM)DQL on user.eventsTo build in the query
DatadogFunnels screen, alertsSuccessive steps
New RelicNRQL funnel()Not enforced

Sources #

Related articles

Website monitoring: availability, HTTPS monitoring, certificates and performance

Set up website monitoring: availability and HTTPS checks, certificate expiry, response time, Core Web Vitals …

Real User Monitoring (RUM) and Core Web Vitals: measuring what users experience

Why observe the front end, what RUM measures, Core Web Vitals (LCP, INP, CLS) and their thresholds, and the d…

Grafana Faro: architecture and installation

Install Grafana Faro: the web SDK (npm or CDN), Alloy's faro.receiver, sending to Loki and Tempo, and configu…

Keep learning

Support us

♥ SUPPORT US

Support RousselTM Learning

Courses, articles, labs and certificates stay free with no intrusive ads. A voluntary contribution, of any amount you choose, pays for hosting, lab environments and new content.

Contribute with PayPal Any amount · card or PayPal accountLearn more
QR code PayPal
Or scan this QR code with your phone

Thank you, every contribution counts!