The client periodically emits a performance_report analytics event (via Segment) describing CPU/GPU frame timing, hiccups, and memory. It is produced by PerformanceAnalyticsSystem from data collected by Profiler.
This is runtime telemetry sent from real sessions. It is not the local Performance Benchmark PDF tool, and it is separate from Diagnostics (ReportHub/Sentry logging).
PerformanceAnalyticsSystemruns in theInitializationSystemGroup(afterDebugViewProfilingSystem).- The event fires every
PerformanceReportIntervalseconds of played time. The shippedAnalyticsConfiguration.assetuses 15 s; the code default and the Playground asset use 1 s. - Collection is paused whenever the realm is not configured, the loading stage is not
Completed, or the app is unfocused. Loading screens and backgrounded time are intentionally excluded.
Both frame-time families read from Unity ProfilerRecorder ring buffers (near-zero overhead, consistent with the Unity Profiler):
- Main thread (
ProfilerCategory.Internal, "Main Thread"): CPU main-thread time. This is not full presented-frame time and not GPU time. Feeds thehiccups_*and*_frame_timefields. - GPU (
ProfilerCategory.Render, "GPU Frame Time"): feeds thegpu_*fields.
The buffer holds up to 1024 frames (FRAME_BUFFER_SIZE).
A hiccup is any single frame whose sampled thread time exceeds the effective threshold:
threshold = max(50 ms, HICCUP_TARGET_FRAME_TIME_MULTIPLIER x targetFrameTime)
with HICCUP_TARGET_FRAME_TIME_MULTIPLIER = 2 and the target frame time derived from Application.targetFrameRate.
targetFrameRate <= 0(vsync or uncapped, which run at >= 60 FPS in practice) → the target-relative bar is below the floor, so the threshold stays at the 50 ms floor.- Explicitly capped configs raise the bar so their normal cadence is not counted as hiccups.
| Target | 2x target | Applied threshold (50 ms floor) |
|---|---|---|
| 120 / 90 / 60 fps | <= 33 ms | 50 ms |
| 40 fps | 50 ms | 50 ms |
| 30 fps | 67 ms | 67 ms |
The threshold applied to each report is emitted as hiccups_threshold, so counts can be interpreted and compared across cohorts. The same threshold is used for CPU and GPU hiccups.
Each report describes exactly its own PerformanceReportInterval. After every report (and whenever collection stops on a teleport/unfocus), both the custom frame-time buffers and the hiccup ProfilerRecorders are reset. ProfilerRecorder has no public flush, so the recorders are disposed and recreated (see Profiler.ResetHiccupRecorders). Consequences:
- No frame is counted in more than one report.
- Stale pre-transition frames are not attributed to the next realm/scene.
- The on-screen debug HUD reads the same recorders, so its hiccup counter reflects the current (post-reset) window rather than a trailing 1024 frames.
Because the window can be shorter than 1024 frames (warm-up, or low framerate), hiccups_samples_amount reports the actual frame count so consumers can normalize (e.g. per-1000-frames or per-second) themselves.
Main-thread hiccups (all times in ms):
| Field | Meaning |
|---|---|
hiccups_in_thousand_frames |
Count of hiccup frames in the window (not normalized to 1000; see hiccups_samples_amount). |
hiccups_time |
Sum of the full duration of each hiccup frame. |
hiccups_excess_time |
Sum of each hiccup frame's excess over the threshold ("time lost" beyond the bar). |
hiccups_min / hiccups_max / hiccups_avg |
Duration stats of hiccup frames. |
hiccups_samples_amount |
Frames the window was measured over. |
hiccups_threshold |
The threshold actually applied this report. |
Frame-time family: min_frame_time, max_frame_time, mean_frame_time, frame_time_percentile_{5,10,20,50,75,80,90,95}, samples, samples_amount.
GPU family: the same fields with a gpu_ prefix (including gpu_hiccups_excess_time and gpu_hiccups_samples_amount). GPU hiccups use the same threshold as the main thread, so there is no separate gpu_hiccups_threshold.
Plus quality/memory context: quality_level, player_count, total_time, JS heap (jsheap_*, running_v8_engines), and process memory (total_used_memory, system_used_memory, gc_used_memory, total_gc_alloc).
hiccups_in_thousand_framesis a count, not a rate. Normalize withhiccups_samples_amountbefore comparing windows or cohorts.hiccups_timeincludes the whole hiccup frame; usehiccups_excess_timefor "time lost to hiccups".- Because the threshold scales with the target frame rate, hiccup counts on intentionally capped configs are lower than a fixed 50 ms bar would produce. This is deliberate: it measures stalls relative to each config's expected cadence. Use
hiccups_thresholdwhen comparing across cohorts. hiccups_maxremains a good "did the client freeze" signal.