# Tools Unity Profiling

> Unity profiling patterns including ProfilerMarkers, FrameTimingManager, memory profiling, and performance budgets.

- **Type:** Skill
- **Install:** `agentstack add skill-tjboudreaux-cc-plugin-unity-gamedev-tools-unity-profiling`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [tjboudreaux](https://agentstack.voostack.com/s/tjboudreaux)
- **Installs:** 0
- **Category:** [Agent Skills](https://agentstack.voostack.com/c/agent-skills)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [tjboudreaux](https://github.com/tjboudreaux)
- **Source:** https://github.com/tjboudreaux/cc-plugin-unity-gamedev/tree/main/skills/tools-unity-profiling

## Install

```sh
agentstack add skill-tjboudreaux-cc-plugin-unity-gamedev-tools-unity-profiling
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# Unity Profiling

## Overview

Unity provides comprehensive profiling tools for CPU, GPU, memory, and frame timing analysis. This skill covers instrumentation patterns and performance optimization workflows.

## When to Use

- Identifying performance bottlenecks
- Tracking memory allocations
- Measuring frame times
- Implementing performance budgets
- Detecting hitches and stutters

## ProfilerMarkers

### Basic ProfilerMarker

```csharp
using Unity.Profiling;

public class CombatSystem
{
    private static readonly ProfilerMarker s_UpdateMarker = new(
        ProfilerCategory.Scripts,
        "CombatSystem.Update"
    );
    
    public void Update()
    {
        s_UpdateMarker.Begin();
        try
        {
            ProcessCombat();
        }
        finally
        {
            s_UpdateMarker.End();
        }
    }
}
```

### Auto-Dispose Pattern

```csharp
public class AbilitySystem
{
    private static readonly ProfilerMarker s_ExecuteMarker = new("AbilitySystem.Execute");
    
    public void ExecuteAbility(Ability ability)
    {
        using (s_ExecuteMarker.Auto())
        {
            ability.Activate();
            ApplyEffects();
            PlayAnimation();
        }
    }
}
```

### Nested Markers

```csharp
public class GameLoop
{
    private static readonly ProfilerMarker s_UpdateMarker = new("GameLoop.Update");
    private static readonly ProfilerMarker s_PhysicsMarker = new("GameLoop.Physics");
    private static readonly ProfilerMarker s_AIMarker = new("GameLoop.AI");
    private static readonly ProfilerMarker s_RenderMarker = new("GameLoop.Render");
    
    public void Update()
    {
        using (s_UpdateMarker.Auto())
        {
            using (s_PhysicsMarker.Auto())
            {
                UpdatePhysics();
            }
            
            using (s_AIMarker.Auto())
            {
                UpdateAI();
            }
            
            using (s_RenderMarker.Auto())
            {
                PrepareRendering();
            }
        }
    }
}
```

### ProfilerMarker with Metadata

```csharp
private static readonly ProfilerMarker s_LoadAssetMarker = new(
    ProfilerCategory.Loading,
    "LoadAsset",
    new ProfilerMarkerDataUnit[]
    {
        ProfilerMarkerDataUnit.Bytes,
        ProfilerMarkerDataUnit.Count
    }
);

public void LoadAsset(string path)
{
    using (s_LoadAssetMarker.Auto())
    {
        // Marker metadata captures additional info
        var asset = Resources.Load(path);
    }
}
```

## FrameTimingManager

### Basic Frame Timing

```csharp
public class FrameTimeTracker
{
    private readonly FrameTiming[] _frameTimings = new FrameTiming[1];
    
    public void CaptureFrameTiming()
    {
        if (!FrameTimingManager.IsFeatureEnabled())
        {
            Debug.LogWarning("FrameTimingManager not available");
            return;
        }
        
        FrameTimingManager.CaptureFrameTimings();
        var count = FrameTimingManager.GetLatestTimings(1, _frameTimings);
        
        if (count > 0)
        {
            var timing = _frameTimings[0];
            
            Debug.Log($"CPU Frame Time: {timing.cpuFrameTime:F2}ms");
            Debug.Log($"CPU Main Thread: {timing.cpuMainThreadFrameTime:F2}ms");
            Debug.Log($"CPU Render Thread: {timing.cpuRenderThreadFrameTime:F2}ms");
            Debug.Log($"GPU Frame Time: {timing.gpuFrameTime:F2}ms");
            Debug.Log($"Present Wait: {timing.cpuMainThreadPresentWaitTime:F2}ms");
        }
    }
}
```

### Continuous Frame Monitoring

```csharp
public class PerformanceMonitor : MonoBehaviour
{
    private readonly FrameTiming[] _frameTimings = new FrameTiming[30];
    
    private float _avgCpuTime;
    private float _avgGpuTime;
    private float _maxFrameTime;
    
    private void Update()
    {
        if (!FrameTimingManager.IsFeatureEnabled()) return;
        
        FrameTimingManager.CaptureFrameTimings();
        var count = FrameTimingManager.GetLatestTimings(_frameTimings.Length, _frameTimings);
        
        if (count > 0)
        {
            float totalCpu = 0, totalGpu = 0;
            _maxFrameTime = 0;
            int gpuCount = 0;
            
            for (int i = 0; i  0)
                {
                    totalGpu += t.gpuFrameTime;
                    gpuCount++;
                }
                
                var frameTime = Mathf.Max(t.cpuFrameTime, t.gpuFrameTime);
                _maxFrameTime = Mathf.Max(_maxFrameTime, frameTime);
            }
            
            _avgCpuTime = totalCpu / count;
            _avgGpuTime = gpuCount > 0 ? totalGpu / gpuCount : 0;
        }
    }
    
    public (float cpu, float gpu, float max) GetFrameTimes()
    {
        return (_avgCpuTime, _avgGpuTime, _maxFrameTime);
    }
}
```

## Memory Profiling

### Memory Snapshot

```csharp
using UnityEngine.Profiling;

public class MemoryTracker
{
    public MemorySnapshot CaptureSnapshot()
    {
        return new MemorySnapshot
        {
            TotalAllocated = Profiler.GetTotalAllocatedMemoryLong(),
            TotalReserved = Profiler.GetTotalReservedMemoryLong(),
            TotalUnused = Profiler.GetTotalUnusedReservedMemoryLong(),
            MonoUsed = Profiler.GetMonoUsedSizeLong(),
            MonoHeap = Profiler.GetMonoHeapSizeLong(),
            GfxDriver = Profiler.GetAllocatedMemoryForGraphicsDriver(),
            Timestamp = Time.realtimeSinceStartup
        };
    }
}

public struct MemorySnapshot
{
    public long TotalAllocated;
    public long TotalReserved;
    public long TotalUnused;
    public long MonoUsed;
    public long MonoHeap;
    public long GfxDriver;
    public float Timestamp;
    
    public long TotalMB => TotalAllocated / 1_000_000;
    public long MonoMB => MonoUsed / 1_000_000;
}
```

### GC Allocation Tracking

```csharp
public class GCAllocationTracker
{
    private long _lastGCMemory;
    private int _lastGCCount;
    
    public void BeginFrame()
    {
        _lastGCMemory = GC.GetTotalMemory(false);
        _lastGCCount = GC.CollectionCount(0);
    }
    
    public (long allocated, bool gcOccurred) EndFrame()
    {
        var currentMemory = GC.GetTotalMemory(false);
        var currentGCCount = GC.CollectionCount(0);
        
        var allocated = currentMemory - _lastGCMemory;
        var gcOccurred = currentGCCount > _lastGCCount;
        
        if (allocated > 1024) // Log allocations > 1KB
        {
            Debug.LogWarning($"Frame allocated {allocated} bytes");
        }
        
        return (allocated, gcOccurred);
    }
}
```

### GC Notification

```csharp
public class GCNotificationHandler
{
    private bool _isRegistered;
    
    public void Register()
    {
        if (_isRegistered) return;
        
        // Register for full GC notification (Gen2)
        GC.RegisterForFullGCNotification(10, 10);
        _isRegistered = true;
        
        // Start monitoring thread
        StartMonitoringThread();
    }
    
    private void StartMonitoringThread()
    {
        var thread = new Thread(() =>
        {
            while (_isRegistered)
            {
                var status = GC.WaitForFullGCApproach();
                if (status == GCNotificationStatus.Succeeded)
                {
                    // GC is approaching - prepare
                    OnGCApproaching();
                }
                
                status = GC.WaitForFullGCComplete();
                if (status == GCNotificationStatus.Succeeded)
                {
                    // GC completed
                    OnGCComplete();
                }
            }
        });
        thread.IsBackground = true;
        thread.Start();
    }
    
    private void OnGCApproaching()
    {
        Debug.Log("Full GC approaching");
    }
    
    private void OnGCComplete()
    {
        Debug.Log("Full GC complete");
    }
}
```

## Hitch Detection

### Simple Hitch Detector

```csharp
public class HitchDetector : MonoBehaviour
{
    private const float MinorHitchMs = 33.33f;  //  OnHitchDetected;
    
    private float _lastFrameTime;
    
    private void Update()
    {
        var frameTimeMs = Time.unscaledDeltaTime * 1000f;
        
        if (frameTimeMs >= SevereHitchMs)
        {
            OnHitchDetected?.Invoke(frameTimeMs, HitchSeverity.Severe);
        }
        else if (frameTimeMs >= MajorHitchMs)
        {
            OnHitchDetected?.Invoke(frameTimeMs, HitchSeverity.Major);
        }
        else if (frameTimeMs >= MinorHitchMs)
        {
            OnHitchDetected?.Invoke(frameTimeMs, HitchSeverity.Minor);
        }
        
        _lastFrameTime = frameTimeMs;
    }
}

public enum HitchSeverity { Minor, Major, Severe }
```

### Advanced Hitch Tracker

```csharp
public class HitchTracker
{
    private readonly Queue _frameTimes = new();
    private readonly int _windowSize;
    
    private int _minorHitchCount;
    private int _majorHitchCount;
    private int _severeHitchCount;
    private int _totalFrames;
    
    public float HitchRate => _totalFrames > 0 
        ? (float)(_minorHitchCount + _majorHitchCount + _severeHitchCount) / _totalFrames 
        : 0f;
    
    public HitchTracker(int windowSize = 300)
    {
        _windowSize = windowSize;
    }
    
    public void RecordFrame(float frameTimeMs)
    {
        _frameTimes.Enqueue(frameTimeMs);
        _totalFrames++;
        
        // Classify hitch
        if (frameTimeMs >= 100f)
            _severeHitchCount++;
        else if (frameTimeMs >= 50f)
            _majorHitchCount++;
        else if (frameTimeMs >= 33.33f)
            _minorHitchCount++;
        
        // Maintain window
        while (_frameTimes.Count > _windowSize)
        {
            var removed = _frameTimes.Dequeue();
            _totalFrames--;
            
            // Adjust counts
            if (removed >= 100f)
                _severeHitchCount--;
            else if (removed >= 50f)
                _majorHitchCount--;
            else if (removed >= 33.33f)
                _minorHitchCount--;
        }
    }
    
    public HitchStats GetStats()
    {
        return new HitchStats
        {
            TotalFrames = _totalFrames,
            MinorHitches = _minorHitchCount,
            MajorHitches = _majorHitchCount,
            SevereHitches = _severeHitchCount,
            HitchRate = HitchRate,
            WorstFrameTime = _frameTimes.Count > 0 ? _frameTimes.Max() : 0f
        };
    }
}

public struct HitchStats
{
    public int TotalFrames;
    public int MinorHitches;
    public int MajorHitches;
    public int SevereHitches;
    public float HitchRate;
    public float WorstFrameTime;
}
```

## Performance Budgets

### Frame Budget System

```csharp
public class FrameBudgetManager
{
    private readonly Dictionary _budgets = new();
    private readonly Dictionary _actuals = new();
    
    public float TotalBudgetMs { get; } = 16.67f; // 60 FPS target
    
    public void SetBudget(string system, float budgetMs)
    {
        _budgets[system] = budgetMs;
    }
    
    public void RecordActual(string system, float actualMs)
    {
        _actuals[system] = actualMs;
        
        if (_budgets.TryGetValue(system, out var budget) && actualMs > budget)
        {
            Debug.LogWarning($"{system} over budget: {actualMs:F2}ms / {budget:F2}ms");
        }
    }
    
    public bool IsWithinBudget()
    {
        var totalActual = _actuals.Values.Sum();
        return totalActual  TotalBudgetMB;
    }
    
    public float GetBudgetUsagePercent()
    {
        var currentMB = Profiler.GetTotalAllocatedMemoryLong() / 1_000_000;
        return (float)currentMB / TotalBudgetMB * 100f;
    }
}

public enum DeviceTier { Low, Medium, High }
```

## Profiler Counters (Custom)

### ProfilerCounter

```csharp
using Unity.Profiling;

public class CustomProfilerCounters
{
    public static readonly ProfilerCounter EnemyCount = new(
        ProfilerCategory.Scripts,
        "Enemy Count",
        ProfilerMarkerDataUnit.Count
    );
    
    public static readonly ProfilerCounter ActiveEffects = new(
        ProfilerCategory.Scripts,
        "Active Effects",
        ProfilerMarkerDataUnit.Count
    );
    
    public static readonly ProfilerCounter MemoryUsageMB = new(
        ProfilerCategory.Memory,
        "Custom Memory MB",
        ProfilerMarkerDataUnit.Bytes
    );
}

// Usage in Update
CustomProfilerCounters.EnemyCount.Sample(_enemies.Count);
CustomProfilerCounters.ActiveEffects.Sample(_activeEffects.Count);
```

## Best Practices

1. **Use ProfilerMarkers** for custom code sections
2. **Sample FrameTimingManager** for accurate timing
3. **Set performance budgets** early in development
4. **Track allocations** to prevent GC spikes
5. **Detect hitches** in production builds
6. **Profile on target devices** - Editor is not representative
7. **Use Deep Profile sparingly** - Significant overhead
8. **Profile specific scenarios** - Combat, loading, menus
9. **Automate profiling** - CI performance tests
10. **Log outliers** - Capture worst-case frames

## Troubleshooting

| Issue | Investigation |
|-------|--------------|
| Low FPS | Check CPU vs GPU bound in Profiler |
| Hitches | Look for GC, loading, or spikes |
| Memory growth | Track allocations over time |
| GC spikes | Find allocation sources |
| GPU bound | Reduce draw calls, shader complexity |
| CPU bound | Optimize scripts, reduce Updates |

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [tjboudreaux](https://github.com/tjboudreaux)
- **Source:** [tjboudreaux/cc-plugin-unity-gamedev](https://github.com/tjboudreaux/cc-plugin-unity-gamedev)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** no
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-tjboudreaux-cc-plugin-unity-gamedev-tools-unity-profiling
- Seller: https://agentstack.voostack.com/s/tjboudreaux
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
