[Studio Beta] Introducing LibMP: the MicroProfiler API

Our analytics pipeline opens CaptureToBufferSync captures through LibMP, where $ProfileBeginOverflow is already returned by FetchTimerDesc. We do use high-cardinality instrumentation, including a dynamic per-frame Animate {enemyCount} enemies scope and over 1,100 possible generated network labels per runtime, so exhausting the table is plausible. Since .gprx/LibMP doesn’t retain the original label, we can see the scope’s duration but cannot identify the responsible code. This also occurs even hours into sessions and in ~90% of recent server captures, so it seems to be genuine scope-table exhaustion rather than the short post-teleport window you described.

Could .gprx/LibMP preserve overflowed labels, or expose the exhausted names and scope-table usage? Without that information, these captures are largely unactionable for diagnosing long-session performance degradation.

There could be an interesting request opportunity for adding a second parameter to debug.profilebegin which would let you add custom labels much like what native microprofiler timers already support, as it seems to not suffer from high-cardinality in the same way as timer names do:

I see, so your instrumentation is truly verbose, and you basically try to use script scope names as labels, which exhausts the pool quickly. So yep, you do indeed need real labels for your use case, which aren’t ready yet (they are used to store the actual scope name in case of overflow). Most games use at most 200 script scopes, so I hoped that 640 slots (ought to be enough for anybody haha) would give us plenty of room for the foreseeable future without introducing too much memory overhead at the same time.

As an alternative, would it be useful for your use case to have arbitrary annotations as 256-character strings that you can attach to the dump every frame, once every N frames or minutes, on level changes, etc.? You could then query which frame IDs have annotations and extract them later.

There could be an interesting request opportunity for adding a second parameter to debug.profilebegin which would let you add custom labels

Yep, that’s a good idea. But usually, when you enter a scope, you don’t know yet what you’ll have to process inside. So either we’d need an additional function to set the labels, or we could just pass them as an argument to profileend.

Arbitrary frame annotations would be very useful (assuming they’re preserved in CaptureToBufferSync/.gprx and exposed through LibMP). We could add compact context when state changes—wave, map, enemy count, loadout, match elapsed time, etc.—and 256 characters should be enough.

That would help us attach the overall game state to a frame—for example, that it happened during wave 24 with 400 enemies. However, the annotation would belong to the whole frame. If that frame contains several $ProfileBeginOverflow scopes with different durations, we still wouldn’t be able to tell which one was Animate, which was a network contract, or which annotation value belonged to each scope.

We’d still benefit from a separate label attached directly to each timed scope without consuming another scope-name slot. We often know that label when entering the scope, but a setter or optional value passed to profileend would cover cases where it’s only known after the work has been processed. I think profileend is the more ergonomic choice, but either or would be a huge help regardless.

Is the frame-annotation idea something that already exists internally or is planned, or are you currently just exploring whether an API like that would be useful? And would those annotations be readable from CaptureToBufferSync captures through LibMP?

Frame annotations are easier to implement than the full Luau-to-MP per-scope labels pipeline, and I’m wondering if moving some context into them could reduce your need for unique scope names by 2-3x. That way we might avoid the overflow while still having all their names as unique/findable timer names. With true labels, you’d be able to save much more than 3x, I assume?

Yes. We could move frame-wide state such as enemy count, wave, match phase, and possibly loadout context into annotations, then replace names like Animate {enemyCount} enemies with a static Animate enemies scope. That would remove hundreds of possible names and make annotations useful for correlating spikes with gameplay state.

The generated network scopes are the larger limitation. Multiple remotes and operations can run in one frame, so a frame annotation cannot reliably identify which context belongs to each individual timer. True per-scope labels would let us use a small set of stable timer names and attach the remote/contract/operation as the label. That should reduce timer-name cardinality by far more than 3x—likely from over a thousand possible names to tens—while preserving the exact attribution we need.

1 Like

Hey @ZenMa1n ! Another error from our analytics over the past 24 hours:

ScriptMemory requires STUDIOPLAT37936 flag

This is from our graph which shows luau heap increases. Just flagging in case this isn’t known!