getsentry / sentry-java

A Sentry SDK for Java, Android and other JVM languages.
https://docs.sentry.io/
MIT License
1.14k stars 430 forks source link

Attach custom child spans to the cold app start transaction #3581

Open zhukic opened 1 month ago

zhukic commented 1 month ago

Problem Statement

Hi!

I use the performance v2 feature, and as the next step, I'd like to measure specific operations time during the application.load operation, for example, and enrich the transaction with more detailed spans. I haven't found the proper way to do this at the moment. Could you please suggest if there is some workaround to do that using the current API(custom callbacks or event processor?)

Expected behaviour: possibility to add child spans to the existing spans(application.load, activity.load, etc.) during cold app start.

SDK version - 7.9.0

Thank you!

Solution Brainstorm

No response

zhukic commented 1 month ago

I also have some "Missing instrumentation" spans. I know which operations causes them and I'd like to have possibility to mark them explicitly as the child spans.

image

markushi commented 1 month ago

@zhukic thanks for reaching out! Yes, that's something we've been considering for a while, let me discuss this internally!

In the meantime you could utilize EventProcessors to add your custom spans, here's a snippet: Beware these are internal APIs, so they might not be stable across releases.

SentryAndroid.init(this) { options ->
            options.apply {
                dsn = ...
                addEventProcessor(object : EventProcessor {
                    @Suppress("UnstableApiUsage")
                    override fun process(
                        transaction: SentryTransaction,
                        hint: Hint
                    ): SentryTransaction {
                        transaction.contexts.trace?.traceId?.let { traceId ->
                            transaction.spans
                                .firstOrNull { it?.op.equals("app.start.cold") }
                                ?.let { appStartSpan ->
                                    val parentSpanId = appStartSpan.spanId

                                    val startTimeStamp = 0.0 // unix time in seconds
                                    val endTimeStamp = 0.0 // unix time in seconds
                                    val op = "component.op"
                                    val description = "description"

                                    transaction.spans.add(
                                        SentrySpan(
                                            startTimeStamp,
                                            endTimeStamp,
                                            traceId,
                                            SpanId(),
                                            parentSpanId,
                                            op,
                                            description,
                                            SpanStatus.OK,
                                            null,
                                            emptyMap(),
                                            emptyMap(),
                                            emptyMap(),
                                            emptyMap(),
                                        )
                                    )
                                }
                        }
                        return transaction
                    }
                })
            }
        }
markushi commented 1 month ago

@zhukic could you describe your use-case a bit more in detail? Which kind of operations do you want to measure? Is it mainly about the execution of Application.onCreate()?

zhukic commented 1 month ago

@markushi Thank you for the answers!

I'll try the approach with the EventProcessor a bit later.

could you describe your use-case a bit more in detail? Which kind of operations do you want to measure? Is it mainly about the execution of Application.onCreate()?

During the Application.onCreate() I initialize lots of my app's components and it takes some time. I'd like to have a possibility to measure all the initializations separately and attach them as the child spans to the application.load span.

The second use case is described here. It's my code which runs on the UI thread, and currently, it's marked as "Missing instrumentation". I'd also like to have a possibility to attach it as a child span. I guess, this can be also solved by a custom EventProcessor.

romtsn commented 1 month ago

@zhukic thanks for the details, that's very helpful! On a side note - would you be interested if we provided auto-instrumentation for those components that get initialized as part of Application.onCreate? We have some ideas on how to to do that, although it may become a bit noisy, but we can think of some configuration/ignorelist.

zhukic commented 1 month ago

@romtsn

would you be interested if we provided auto-instrumentation for those components that get initialized as part of Application.onCreate

Yes, I would!

We have some ideas on how to to do that, although it may become a bit noisy

What do you mean?

I'd expect that I can configure myself which parts of code to make measurable, something like this:

override fun onCreate() {
    super.onCreate()

    //some code

    initialize1()
    initialize2()

    //some code
}

@ApplicationOnCreateSpan(spanOperation = "initialization1")
private fun initialize1() {
    //
}

@ApplicationOnCreateSpan(spanOperation = "initialization2")
private fun initialize2() {
    //
}

and application.load span would have two child spans: initialization1, initialization2

zhukic commented 1 month ago

@markushi @romtsn Hi!

I've tried the solution with the custom EventProcessor, and looks like it works! The only thing that concerns me is that there are no application.load and activity.load spans that, as I see, get attached in PerformanceAndroidEventProcessor which is called after my custom processor.

I see that there is also BeforeSendTransactionCallback which contains all the spans but I'm not sure if it's ok to modify the transaction on this stage.

So the UI looks like this for the spans which are created from Application.onCreate:

Image

But it's good enough for now!

If you have some suggestions - would be glad to hear.

Thank you for help!

markushi commented 4 weeks ago

@zhukic Glad it's working for you! Actually it should be safer to use BeforeSendTransactionCallback instead of a custom EventProcessor, as the BeforeSendTransactionCallback is executed after all processors.