Sentry - Features in progress

Hey there!
I’d like to share my progress with the out-of-the-box performance instrumentation on the Sentry SDK for Unity.
The idea is to hook up to certain events and give you visibility into what is happening and how much it costs.
All of this is open source and you can check it out on Github.

Right now I’ve got the startup and scene loading in working condition and it looks something like this:


During the build process, we look for MonoBehaviours and if they implement the Awake method we add a start and finish call to it. That way we can track the time each Awake took to finish. In the screenshot, you can see the Awake on Game.Encyclopedia took 45ms - so basically 3 frames got dropped. It might now matter during startup all that much but if that scene got loaded additively or I were to instantiate the GameObject during regular play it might end up noticeable.

This is very much work in progress and I appreciate any and all feedback!

2 Likes

A little bit of a progress update:

Let’s say I have my MonoBehaviour and I want to know how long it took to execute:

public class MyMonoBehaviour : MonoBehaviour
{
    private void Awake()
    {
        Debug.Log("This is an Awake call!");
    }
}

then with Sentry I could create spans and surround my code like like so:

public class MyMonoBehaviour : MonoBehaviour
{
    private void Awake()
   {
       SentrySdk.GetSpan()?.StartChild("awake", $"MyMonoBehaviour on {gameObject.name}");

       Debug.Log("This is an Awake call!");

       SentrySdk.GetSpan()?.Finish(SpanStatus.Ok);
   }
}

Now having to do that manually is very cumbersome so the idea is to do that automatically. Ideally, you would not have to think about it and it’s part of the build process.

So for that, I’m using something called IL weaving. During the build process, I grab the compiled Assembly-CSharp.dll and I look for all classes that inherit from MonoBehaviour. I then check if they implement the Awake and if they do I’ll add the span calls.

This is what the original IL looks like:

.method private hidebysig
    instance void Awake () cil managed
{
    // Method begins at RVA 0x2120
    // Header size: 1
    // Code size: 11 (0xb)
    .maxstack 8

    // Debug.Log("This is an Awake call!");
    IL_0000: ldstr "This is an Awake call!"
    IL_0005: call void [UnityEngine.CoreModule]UnityEngine.Debug::Log(object)
    // }
    IL_000a: ret
} // end of method MyMonoBehaviour::Awake

and with the automatic instrumentation we end up with this:

.method private hidebysig

    instance void Awake () cil managed
{
    // Method begins at RVA 0x22b7
    // Header size: 1
    // Code size: 32 (0x20)
    .maxstack 8

    // SentryMonoBehaviour.Instance.StartAwakeSpan(this);
    IL_0000: call class [Sentry.Unity]Sentry.Unity.SentryMonoBehaviour [Sentry.Unity]Sentry.Unity.SentryMonoBehaviour::get_Instance()
    IL_0005: ldarg.0
    IL_0006: callvirt instance void [Sentry.Unity]Sentry.Unity.SentryMonoBehaviour::StartAwakeSpan(class [UnityEngine]UnityEngine.MonoBehaviour)
    // Debug.Log("This is an Awake call!");
    IL_000b: ldstr "This is an Awake call!"
    IL_0010: call void [UnityEngine.CoreModule]UnityEngine.Debug::Log(object)
    // SentryMonoBehaviour.Instance.FinishAwakeSpan();
    IL_0015: call class [Sentry.Unity]Sentry.Unity.SentryMonoBehaviour [Sentry.Unity]Sentry.Unity.SentryMonoBehaviour::get_Instance()
    IL_001a: call instance void [Sentry.Unity]Sentry.Unity.SentryMonoBehaviour::FinishAwakeSpan()
    // }
    IL_001f: ret
} // end of method MyMonoBehaviour::Awake

To note is that I’m using a static helper here instead of directly recreating the call in IL simply because it’s a lot more readable and maintainable to keep as much in C# as possible.

If you want to take a look at it yourself you can check out the PR here: feat: Auto Instrumentation for Awake by bitsandfoxes · Pull Request #998 · getsentry/sentry-unity · GitHub

Hey, another update: I’ve been working on getting the Hierarchy from the Editor into the error event.
The idea is to provide as much context as possible at the time of an error. And what’s more natural than the actual scene hierarchy.

It’s very much work in progress right now but the first impressions are really promising.

Right now there are still a bunch of open questions.

  • How to grab the components off the GameObjects in a meaningful way? What can be properly serialize? Just their names?

  • How to deal with size limitations? How many GameObjects? How deep?

  • Does that have to happen on the main thread?

  • Instead of traversing and creating the hierarchy at the time of an error, can that be done in a different way? ILWeaving for Initialize and AddComponent come to mind. So the SDK tracks the changes made to the scene instead of having to re-create the data from scratch every single time.

As always, the PR can be seen here: feat: View Hierarchy by bitsandfoxes · Pull Request #1169 · getsentry/sentry-unity · GitHub