Not signed in
Copyright © 2026 Unity Technologies
We can't obtain timing data of C# jobs using the Measure.ProfilerMarkers() function in a [Performance] test. See this slack thread for context and discussion of this issue.
Steps to reproduce:
Run any simulation that contains a C# job and try to obtain accumulated frame time of said job using the ProfilerMarker() in a [Performance] test.
Example steps:
class ColliderParadePerformanceTest : UnityPhysicsSamplesTest { [SetUp] public void SetUp() { ConfigureSimulation(World.DefaultGameObjectInjectionWorld, SimulationType.UnityPhysics); } [UnityTest, Performance] [Timeout(10000000)] public override IEnumerator LoadScenes([Values("Collider Parade - Basic")] string scenePath) { SceneManager.LoadScene(scenePath); var sampleGroups = new[] { // This sample group will return non-zero timing data which is expected. new SampleGroup("Default World Unity.Entities.FixedStepSimulationSystemGroup"), // This sample group will return zero timing data which is unexpected. new SampleGroup("Broadphase:StaticVsDynamicFindOverlappingPairsJob (Burst)"), }; yield return Measure.Frames() .ProfilerMarkers(sampleGroups) .WarmupCount(10) .MeasurementCount(360) .Run(); } }
Actual results:
The data returned by the performance test for the C# job profiler markers read zero for every frame. See below.
[Internal link]
Expected results:
We should get non-zero timing data for every frame in which the job is run.
Reproducible with versions:
Not reproducible with versions:
N/A
Can’t test with versions:
N/A
Tested on (OS):
Windows 10
Notes:
The timing data is available as I was able to show by adding the following MonoBehavior script to the scene "ColliderParade - Basic.unity".
using Unity.Profiling;
using UnityEditor.Profiling;
using UnityEditorInternal;
using UnityEngine;
using UnityEngine.Profiling;
namespace Tests.Performance.TreeLifetimePerformanceTest
{
public class SomeMonoBehavior : MonoBehaviour
{
ProfilerRecorder recorder;
Recorder behaviourUpdateRecorder;
Recorder behaviourUpdateRecorder2;
void Start()
{
behaviourUpdateRecorder = Recorder.Get("Broadphase:StaticVsDynamicFindOverlappingPairsJob (Burst)");
behaviourUpdateRecorder.enabled = true;
behaviourUpdateRecorder2 = Recorder.Get("Default World Unity.Entities.FixedStepSimulationSystemGroup");
behaviourUpdateRecorder2.enabled = true;
recorder = ProfilerRecorder.StartNew(ProfilerCategory.Scripts, "Broadphase:StaticVsDynamicFindOverlappingPairsJob (Burst)");
}
void Update()
{
// Does not work. Time is zero.
if (behaviourUpdateRecorder.isValid)
Debug.Log("BehaviourUpdate time: " + behaviourUpdateRecorder.elapsedNanoseconds);
// Does work. Time is non-zero.
if (behaviourUpdateRecorder2.isValid)
Debug.Log("BehaviourUpdate2 time: " + behaviourUpdateRecorder2.elapsedNanoseconds);
// Does not work. Time is zero.
Debug.Log("ProfilerRecorder time: " + recorder.CurrentValue);
// Low-level method works. Time is non-zero.
var totalTime = 0.0f;
for (int j = 0;; ++j)
{
using RawFrameDataView frame = ProfilerDriver.GetRawFrameDataView(Time.frameCount, j);
if (!frame.valid)
break;
var markerId = frame.GetMarkerId("Broadphase:StaticVsDynamicFindOverlappingPairsJob (Burst)");
int sampleCount = frame.sampleCount;
for (int i = 0; i < sampleCount; ++i)
{
if (markerId != frame.GetSampleMarkerId(i))
continue;
var time = frame.GetSampleTimeMs(i);
totalTime += time;
}
}
Debug.Log("FrameData time: " + totalTime);
}
}
}
In the code above, the job timing data can only be obtained by using the low level profiler API: RawFrameDataView frame = ProfilerDriver.GetRawFrameDataView(Time.frameCount, j);
All other attempts to obtain the data, both using the ProfilerRecorder and the legacy Recorder fail.
See here for successful retrieval of the results through the above mentioned low level API:
[Internal link]
We can see that profiler markers are created for such jobs in the engine [Internal link]:
#if ENABLE_PROFILER char* jobName; size_t jobNameLen = strlen(data->async.jobName); ALLOC_TEMP_AUTO(jobName, jobNameLen + std::max(strlen(kBurstPostfix), strlen(kCombinePostfix)) + 1); strcpy(jobName, data->async.jobName); data->async.profilerInfoManaged = GetOrCreateProfilerMarker(jobName, profiling::kProfilerManagedJobs); strcpy(jobName + jobNameLen, kBurstPostfix); data->async.profilerInfoBurst = GetOrCreateProfilerMarker(jobName, profiling::kProfilerBurstJobs); strcpy(jobName + jobNameLen, kCombinePostfix); data->async.profilerInfoCombineJob = GetOrCreateProfilerMarker(jobName, profiling::kProfilerManagedJobs); #endif
Sign in to see your voted issues