Is it possible to obtain User-Centric Metrics with Custom Transactions?

Hi there, I'm a front-end developer with limited experience, therefore I'm a bit stuck at the moment.

I've applied Elastic RUM for the front-end (Angular) part of our single-page application. Our use case is to obtain performance metrics when users are traversing / clicking through the application. Unfortunately, because it is a single-page application, the user-centric metrics obtained during page-load don't suffice. We don't make use of the Angular router either.
From reading the documentation I figured I could use Custom Transactions to obtain user-centric metrics:

However, when attempting to use the code above an error is thrown, referring to { blocking: true } at line 4:

public createCustomEvent(data: eventData, event: string): void {
    const transaction = apm.startTransaction(event, 'custom event', { managed: true });
    if (transaction) {
      const span = transaction.startSpan('async-task', 'app', { blocking: true })
      if (span) {
        setTimeout(() => {
          span.end()
          /**
           * This would also end the managed transaction once all the blocking spans are completed and
           * transaction would also contain other timing information and spans similar to auto
           * instrumented transactions like `page-load` and `route-change`.
           */
        }, 1000)
      }
    }
    const url = 'URL_ADDRESS';
    if (transaction) {
    const httpSpan = transaction.startSpan('GET ' + url, 'external.http');
      if (httpSpan) {
        fetch(url)
        .then((resp) => {
            if (!resp.ok) {
            apm.captureError(new Error(`fetch failed with status ${resp.status} ${resp.statusText}`));
            }
            apm.addLabels(this.convertToLabels(data));
            httpSpan.end();
            transaction.end();
        });
      }
    }
  }

This error is correct, because this is what the interface SpanOptions looks like:

    interface SpanOptions {
      /**
      * undocumented, might be removed in future versions
      */
      startTime?: number
      sync: boolean
    }

I tried replacing 'blocking' with 'sync', but this had no effect.

Am I on the right track here, or is it simply not possible to use Custom Transactions to obtain user-centric metrics?

Hi @randy_a,

The issue is we are not exposing the type information for the new Blocking Transaction and Span API we have added recently. I have created an issue in the agent to fix that - expose blocking transaction/span type information in typings file · Issue #995 · elastic/apm-agent-rum-js · GitHub

However, it should not block you and you can use any type to fix the error for time being until the next release is done to expose these typings.

const span = transaction.startSpan('async-task', 'app', { blocking: true } as any)

The custom transactions with managed: true would capture all timing information for requests and also other available user centric metrics like Long tasks, User Timing API, etc.

Please do let us know if you need any other help.

Thanks,
Vignesh

1 Like

Thank you for the -swift- response @vigneshshanmugam!

The any type did indeed fix the error, thanks!
However, changing my custom transaction to managed: true hasn't captured any user centric metrics yet, so I must be doing something wrong.

When it's a custom transaction like this, I receive the data as expected after clicking a button with labels.name: Selecteren.

public createCustomEvent(data: eventData, event: string): void {
    const transaction = apm.startTransaction(event, 'custom event');
    const url = 'URL_ADDRESS';
    if (transaction) {
      const httpSpan = transaction.startSpan('GET ' + url, 'external.http');
      if (httpSpan) {
        fetch(url)
        .then((resp) => {
            if (!resp.ok) {
              apm.captureError(new Error(`fetch failed with status ${resp.status} ${resp.statusText}`));
            }
            apm.addLabels(this.convertToLabels(data));
            httpSpan.end();
            transaction.end();
        });
      }
    }
  }

When changing my Custom Transaction to a Managed Transaction I expected to do something like this (from reading: Custom Transactions | APM Real User Monitoring JavaScript Agent Reference [5.x] | Elastic):

public createManagedEvent(data: eventData, event: string): void {
    const transaction = apm.startTransaction(event, 'custom event',  { managed: true });
    const url = 'URL_ADDRESS';
    if (transaction) {
      const span = transaction.startSpan('async-task', 'app', { blocking: true } as any )
      if(span) {
        setTimeout(() => {
          const httpSpan = transaction.startSpan('GET ' + url, 'external.http');
          if (httpSpan) {
            fetch(url)
            .then((resp) => {
                if (!resp.ok) {
                apm.captureError(new Error(`fetch failed with status ${resp.status} ${resp.statusText}`));
                }
                apm.addLabels(this.convertToLabels(data));
                httpSpan.end();
                transaction.end();
            });
          }
          span.end()
          /**
          * This would also end the managed transaction once all the blocking spans are completed and
          * transaction would also contain other timing information and spans similar to auto
          * instrumented transactions like `page-load` and `route-change`.
          */
        }, 1000);
      }
    }
  }

But this gives me pretty much the same output as before, with no user-centric metrics such as firstContentfulPaint / longtask/etc. data:

I'd really appreciate it if you could tell me what I'm doing wrong.

Okay, I've been over it during the weekend and I think I'm grasping it a bit more.

I think all I have to do is adjust my original Custom Event like this to turn it into a Managed Event:

public createManagedEvent(data: eventData, event: string): void {
  const transaction = apm.startTransaction(event, 'managed event', { managed: true});
  const url = 'URL_ADDRESS';
  if (transaction) {
    const httpSpan = transaction.startSpan('GET ' + url, 'external.http', { blocking: true } as any);
    if (httpSpan) {
      fetch(url)
      .then((resp) => {
          if (!resp.ok) {
          apm.captureError(new Error(`fetch failed with status ${resp.status} ${resp.statusText}`));
          }
          apm.addLabels(this.convertToLabels(data));
          httpSpan.end();
          transaction.end();
      });
    }
  }
}

This does give me a bit more output in the Trace Sample visualization:

I suppose Long Tasks and User Timing would show up in this waterfall picture above?

I was hoping I could get user-centric metrics such as First Input Delay and First Contentful Paint, but after diving deeper into the topic I think this is pretty difficult data to gather from our Single-Page-Application.

Hi @randy_a ,

Metrics like First Input Delay and First Contentful paint are reported for the initial page load navigation (hard navigation) which is why they show up for page-load transaction and not for subsequent transactions like route-change (soft navigation). The only metrics that you should be able to see for custom managed transactions would be ResourceTiming, UserTiming and Longtasks if available and reported by the browser.

You can double check if browsers report those metrics during the lifetime of the managed transaction by checking chrome devtools or by using the Performance API.

Thanks,
Vignesh

Thanks @vigneshshanmugam , appreciate the feedback!

One last question though, are you 100% certain that route-changes don't show metrics such as First Input Delay and First Contentful Paint?
I'm asking because another (front-end) team within our company has also implemented Elastic RUM and unlike us, their project does make use of angular routing. When I look at their APM transactions I see those paths/routes and they do show First Input Delay/First Contentful Paint when I inspect them.

So good news for them at least. :wink:

Hmmm Interesting, So when using the Angular package, we use the Angular route events to listen for all different SPA navigations, so it could be related to that.

But for the metrics itself, you can check this document - Supported Technologies | APM Real User Monitoring JavaScript Agent Reference [5.x] | Elastic. All metrics like FCP, LCP, FID measure the load responsiveness and does not really work for route-change events.

Thanks,
Vignesh

1 Like

This topic was automatically closed 28 days after the last reply. New replies are no longer allowed.