Add profile module — sessions, vitals, assert, remote (Android verified)#44
Draft
Add profile module — sessions, vitals, assert, remote (Android verified)#44
Conversation
…emote
New `unityctl profile` family for capturing Unity Editor and remote-player
performance data from the CLI:
profile list-stats Enumerate built-in counters (~3,790 in test project)
profile start/stop/status Session lifecycle with --max-duration auto-stop cap
profile capture One-shot start+wait+stop, with optional --save .data
profile vitals Curated 5-number report (avg/p99 frame, GC, draws, GPU)
profile assert CI/regression gate: exit 1 if thresholds breached
profile snapshot Memory snapshot via com.unity.memoryprofiler
profile targets List editor + connected players
profile connect <url> Direct-URL connect (e.g. adb-forwarded Android)
Local mode samples Unity.Profiling.ProfilerRecorder per editor tick. Remote
mode (autoconnect-profiler builds, live device) drives the editor's profiler
buffer and walks frames with RawFrameDataView.GetCounterValueAsLong on stop —
same JSON shape, real device numbers. Verified end-to-end on a connected
Samsung SM-A266B running an IL2CPP+ARM64 dev build.
Stat name aliases (main, render, gpu, drawcalls, gc-alloc, ...) give agents
stable handles independent of Unity's slightly inconsistent counter naming.
Side effects:
- Switching unity-project to Android target updates URP/GraphicsSettings/
ProjectSettings (productName, applicationIdentifier, etc.) — kept.
- Set System.Globalization invariant culture in CLI Program.cs so threshold
flags like --p99-frame-ms 16.7 parse correctly on non-en locales.
- unity-project/Assets/Editor/AndroidProfileBuild.cs is a reusable helper
that produces a Dev + AutoConnect Profiler APK from BuildPipeline.
Known limitation: Memory counters (System Used Memory, GC Allocated In Frame)
return 0 from Android — Unity's player doesn't stream the Memory profiler
module by default. Markers exist; values aren't populated. CPU/GPU/draw
counters work end-to-end.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A new
unityctl profilecommand family for capturing Unity Editor and connected-player performance data. Designed around two shapes: structured JSON for agents (vitals, assert) and shareable artifacts for humans (.datafor the Profiler window,.snapfor Memory Profiler).Commands
Stat name aliases (
main,render,gpu,drawcalls,gc-alloc, …) give agents stable handles independent of Unity's slightly inconsistent counter naming.Design decisions
--max-durationcap — same shape asxctrace,dotnet-trace, Puppeteer'stracing.start/stop. Lets agents compose: start → run scenario via other unityctl commands → stop → read summary.--saveproduces a Profiler-window-openable.datafor humans to look at.ProfilerRecordersampling, no.datafile.--saveflag drivesProfilerDriver.SaveProfile.profile snapshotfor memory.profile targetsenumerates connections,--target <id>selects,profile connect <host:port>for explicit URL connect (Android via adb forward).--hitch-ms 33.3for absolute thresholds. Returned as{frameIndex, frameTimeMs}array on stop.Local vs remote sampling — the interesting bit
Unity.Profiling.ProfilerRecorderonly samples the local editor process. WhenProfilerDriver.connectedProfilerpoints at a remote target (e.g. an Android dev build with autoconnect), the recorder returns zeros.The fix: detect remote at session start, switch code paths.
ProfilerRecorderper requested stat, sample once perEditorApplication.updatetick.ProfilerDriverto buffer streamed remote frames, snapshot the start frame index, and onStop()walk every frame in the buffer withRawFrameDataView.GetCounterValueAsLong(), mapping by stat name. Units are looked up locally (built-in counter names share units across editor and player). Same summary JSON comes back, populated.The human-readable output now also prints
target: REMOTE — <connection name>so you can't confuse local vs device data.Verified end-to-end on Android
Built a Development + AutoConnect Profiler APK (
unity-project/Assets/Editor/AndroidProfileBuild.cs, IL2CPP/ARM64), installed on a connected Samsung SM-A266B, and ran:— real device numbers, ~30 fps cap (Android default), reasonable GPU headroom.
profile assertPASS / FAIL exit codes verified on the device. Saved.dataopens cleanly in the Profiler window with the connection dropdown showing the device.Things worth flagging
System Used Memory,GC Allocated In Frame) come back as 0 from Android. Markers exist in the streamed frames, values aren't populated — Unity's player doesn't ship the Memory profiler module by default. CPU/GPU/draw counters work fine. Likely fixable viaProfiler.SetAreaEnabled(ProfilerArea.Memory, true)at runtime in the player or a build flag.EditorApplication.delayCalldid not fire reliably in our test session. Worked around by callingBuildPipeline.BuildPlayersynchronously insidescript evalwith a long timeout. Worth investigating whether it's a Unity quirk or interaction with the script-eval wrapper.uc dialog listand dismissed it. UX improvement: the build helper could auto-dismiss known-safe dialogs, orprofile capturecould proactively check for blocking dialogs.CultureInfo.InvariantCultureinProgram.csso threshold flags like--p99-frame-ms 16.7parse correctly on non-en locales (was hitting Norwegian,decimal separator).Next steps (intentionally not in this PR)
.datacontent to the{name, ph, ts, dur, tid}event format. Unlocks drag-drop into ui.perfetto.dev / speedscope.app, zero-install shareable viewers.profile watch— live counter dashboard streamingProfilerRecorder.CurrentValueat 2-Hz, likedotnet-counters monitor. Cheap to build.vitalsalso shows GC alloc / system memory.--targetUX — when no--targetis passed but a remote is currently connected, currently we silently profile the remote. Thetarget: REMOTEline makes this visible, but explicit--target editorto force-local might be worth adding.Test plan
profile list-stats— 3,790 counters enumeratedprofile capture --duration Nin editor play mode — real numbers, hitch detectionprofile capture --save run.data— opens cleanly in Profiler windowprofile vitals— curated reportprofile assert— pass + fail with correct exit codes (local and remote)profile targets— editor + Android device enumerated--jsonoutput — agent-friendly shape--p99-frame-ms 16.7parses on Norwegian localecom.unity.memoryprofilerinstall (not in this PR's test project)