Use Mali system metrics in the Profiler
You can use the System Metrics Mali package in the Profiler in the following ways:
- Use the Mali System Metrics Profiler module
- Create a custom Profiler module in the Editor
- Create a custom Profiler module with the ProfilerModule API
Use the Mali System Metrics Profiler module
The System Metrics Mali package includes a Profiler module to monitor low-level GPU metrics in the Unity Profiler window.

The Mali System Metrics module.
The chart view displays a high level overview of how the GPU distributed its processing load.
The module's Details view, displays detailed information about the selected frame, such as information about tiling, memory usage, Z tests, and shader instructions. For descriptions of the individual counters, you can hover over their name in the Unity Editor or refer to Mali GPU counters reference.
Create a custom Profiler module in the Editor
Use the Profiler Module Editor to build Profiler modules directly in the Editor that use the Mali stats. A custom module can display additional Mali metrics in a Profiler chart, alongside other built-in or custom Profiler counters.
Connect the Profiler to a Mali GPU device and capture profiling data before you open the Profiler Module Editor. If you open the Profiler Module Editor window first, the Mali metrics counters don't appear in the Available Counters menu.

The Mali Shader Usage module.
Modules you create with the Module Editor aren't part of your Unity project and only appear in your local Editor. To define a custom Profiler Module that's available for all project users, refer to Create a custom Profiler module with the ProfilerModule API.
Create a custom Profiler module with the ProfilerModule API
Use the ProfilerModule API to define a custom Profiler module that uses the Mali counters. You can use this to combine the Mali metrics with other built-in or custom Profiler counters in one module, or to implement a bespoke visualization using the Mali performance data.
The following example uses the Mali metrics alongside custom Profiler counters to correlate the metrics on a single chart.

Custom Profiler Module.
using Unity.Profiling.Editor;
using Unity.Profiling.LowLevel.Unsafe;
using Unity.Profiling.SystemMetrics;
[ProfilerModuleMetadata("Mali Tanks")]
public class MaliTanksProfilerModule : ProfilerModule
{
static readonly ProfilerCounterDescriptor[] k_ChartCounters =
{
GetDescriptorProfilerCounterHandle(SystemMetricsMali.Instance.GpuCycles),
GetDescriptorProfilerCounterHandle(SystemMetricsMali.Instance.GpuVertexAndComputeCycles),
GetDescriptorProfilerCounterHandle(SystemMetricsMali.Instance.GpuFragmentCycles),
GetDescriptorProfilerCounterHandle(SystemMetricsMali.Instance.GpuShaderCoreUtilization),
GetDescriptorProfilerCounterHandle(SystemMetricsMali.Instance.GpuMemoryReadBytes),
new ProfilerCounterDescriptor("Tank Count", Complete.GameStats.TanksProfilerCategory.Name),
new ProfilerCounterDescriptor("Bullet Count", Complete.GameStats.TanksProfilerCategory.Name),
};
public MaliTanksProfilerModule() : base(k_ChartCounters) { }
static ProfilerCounterDescriptor GetDescriptorProfilerCounterHandle(ProfilerRecorderHandle handle)
{
var description = ProfilerRecorderHandle.GetDescription(handle);
return new ProfilerCounterDescriptor(description.Name, description.Category);
}
}
Access the Profiler counters through code
The System Metrics Mali package implements all its metrics as Profiler counters. You can access them through a script using the ProfilerRecorder API.
All Mali counters belong to the same ProfilerCategory, which you can access via SystemMetricsMali.Instance.Category.
The following example uses the ProfilerRecorder API to display some Mali metrics in-game on the screen, including from within a Release build without the Profiler attached.

In-game display.
using System.Collections.Generic;
using System.Text;
using Unity.Profiling;
using Unity.Profiling.LowLevel.Unsafe;
using Unity.Profiling.SystemMetrics;
using UnityEngine;
public class MaliHUD : MonoBehaviour
{
List<HUDEntry> m_Entries;
string m_Text;
GUIStyle m_TextStyle;
StringBuilder m_TextBuilder;
void Awake()
{
m_TextBuilder = new StringBuilder(500);
m_Entries = new List<HUDEntry>()
{
new HUDEntry(SystemMetricsMali.Instance.GpuShaderCoreCycles),
new HUDEntry(SystemMetricsMali.Instance.GpuShaderArithmeticCycles),
new HUDEntry(SystemMetricsMali.Instance.GpuShaderLoadStoreCycles),
new HUDEntry(SystemMetricsMali.Instance.GpuShaderTextureCycles),
};
}
void OnEnable()
{
foreach (var entry in m_Entries)
entry.Recorder.Start();
}
void OnDisable()
{
foreach (var entry in m_Entries)
entry.Recorder.Dispose();
}
void Update()
{
m_TextBuilder.Clear();
foreach (var entry in m_Entries)
{
var value = entry.Recorder.LastValue;
m_TextBuilder.AppendLine($"{entry.Name}: {FormatCount(value)}");
}
m_Text = m_TextBuilder.ToString();
}
void OnGUI()
{
if (m_TextStyle == null)
m_TextStyle = new GUIStyle(GUI.skin.label) { fontSize = 42 };
GUILayout.Label(m_Text, m_TextStyle);
}
static string FormatCount(in long count)
{
if (count < 1000)
return string.Format("{0:D}", count);
else if (count < 1000000) // 1e6
return string.Format("{0:F2}k", count * 1.0e-3);
else
return string.Format("{0:F2}M", count * 1.0e-6);
}
struct HUDEntry
{
public HUDEntry(ProfilerRecorderHandle handle)
{
Recorder = new ProfilerRecorder(handle);
if (!Recorder.Valid)
throw new System.ArgumentException();
var description = ProfilerRecorderHandle.GetDescription(handle);
Name = description.Name;
}
public string Name { get; }
public ProfilerRecorder Recorder { get; }
}
}
Discover available counters at runtime
You can also discover which Mali counters are available on a specific device at runtime using the GetAvailableCounters method. This is useful when you want to adapt your profiling based on the specific Mali GPU capabilities.
using System.Collections.Generic;
using Unity.Profiling;
using Unity.Profiling.LowLevel.Unsafe;
using Unity.Profiling.SystemMetrics;
using UnityEngine;
public class MaliCounterDiscovery : MonoBehaviour
{
void Start()
{
if (!SystemMetricsMali.Instance.Active)
{
Debug.Log("Mali counters not available on this device");
return;
}
// Get all available counters on this device
var availableCounters = new List<ProfilerRecorderHandle>();
SystemMetricsMali.Instance.GetAvailableCounters(availableCounters);
Debug.Log($"Found {availableCounters.Count} available Mali counters:");
foreach (var handle in availableCounters)
{
var description = ProfilerRecorderHandle.GetDescription(handle);
Debug.Log($"- {description.Name} (Category: {description.Category})");
}
// Create recorders for all available counters
var recorders = new List<ProfilerRecorder>();
foreach (var handle in availableCounters)
{
var recorder = new ProfilerRecorder(handle);
if (recorder.Valid)
{
recorder.Start();
recorders.Add(recorder);
}
}
// Use the recorders to collect data...
// Remember to dispose them when done:
// foreach (var recorder in recorders) recorder.Dispose();
}
}