UnityのProfilerの情報を取得する(UnityEditorInternal使用)

# はじめに

サーバサイドの開発がメインだったのでほとんど触ってなかったUnityを最近触り始めた初心者です。

Unityで特定の機能を担うクラスの処理負荷を見る機会があったのですが、UnityのProfilerはUnity上からしか見れなかったり、フレーム単位での確認になるので特定の関数が呼ばれたフレームなんかを探すのがちょっと面倒です。
柔軟に集計したり別の形式のビューで表示したりしたかったので、Profilerに表示された情報をスクリプトで取り出してみました。

# 確認環境

Unity 2020.3.23f1

# 方法

他の記事などを参考にしたところ、 `UnityEditorInternal` を使う方法で取り出すことができるようです。

例えば、フレーム数の一覧は次のようにして取得できます。

```C#
        int frame = UnityEditorInternal.ProfilerDriver.firstFrameIndex;
        while (frame != -1)
        {
            frame = UnityEditorInternal.ProfilerDriver.GetNextFrameIndex(frame);
        }
```

特定のフレームでのヒエラルキー1行ごとのデータは以下のようにして取得できます。

```C#
        var property = new UnityEditorInternal.ProfilerProperty();
        property.SetRoot(frame, HierarchyFrameDataView.columnDontSort, 0);
        string output = "";
        while (property.Next(true))
        {
            int depth = property.depth;

            string name         = property.GetColumn(HierarchyFrameDataView.columnName);
            string totalPercent = property.GetColumn(HierarchyFrameDataView.columnTotalPercent);
            string selfPercent  = property.GetColumn(HierarchyFrameDataView.columnSelfPercent);
            string calls        = property.GetColumn(HierarchyFrameDataView.columnCalls);
            string gcMemory     = property.GetColumn(HierarchyFrameDataView.columnGcMemory);
            string totalTime    = property.GetColumn(HierarchyFrameDataView.columnTotalTime);
            string selfTime     = property.GetColumn(HierarchyFrameDataView.columnSelfTime);
            string warningCount = property.GetColumn(HierarchyFrameDataView.columnWarningCount);

            // 出力例
            output += $"{depth} {name} {totalPercent}\n";
        }
        Debug.Log(output);
```

画像のようなプロファイラの状態では以下のように出力されます。
(画像と若干ずれがありますが、UIでは設定できないソートなしをスクリプト側で使用しているためです)
![ProfilerQiita - SampleScene - PC, Mac & Linux Standalone - Unity 2020.3.23f1 Personal _DX11_ 2021_12_02 22_53_14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535395/ae76242a-f9b1-ea0b-866f-07073f2ed599.png)

```
1 EditorLoop 99.1%
1 PlayerLoop 0.6%
2 Camera.FindStacks 0.0%
2 Camera.Render 0.2%
3 Camera.ImageEffects 0.0%
4 Graphics.Blit 0.0%
5 RenderTexture.ResolveAA 0.0%
3 CullResults.CreateSharedRendererScene 0.0%
4 ReflectionProbeAnchorManagerUpdate 0.0%
5 ReflectionProbeAnchorPositionUpdate 0.0%
4 UnityEngine.CoreModule.dll!UnityEngine.Rendering::SupportedRenderingFeatures.IsLightmapBakeTypeSupportedByRef() 0.0%
3 Culling 0.1%
...
```

上記の2例を組み合わせることで、Profilerに記録された情報を全フレームに渡って取得できます。
今回の目的であった「特定のクラスの処理負荷」では、クラス名の文字列が見つかったdepthを利用することでその配下の関数呼び出しだけをうまいこと取り出すことができます。

# UnityEditorInternalについて

今回利用している `UnityEditorInternal` は、[ソースは公開されている](https://github.com/Unity-Technologies/UnityCsReference) けどUnity側から利用方法などを提供されていない部分です。
Unityのバージョンアップで仕様変更されて使えなくなる可能性があります。

例として、参考にした2つの記事では動作確認したUnityのバージョンによって利用方法が変わっています。

```C#
// 2018ではこれ
property.SetRoot(frame, ProfilerColumn.SelfTime, ProfilerViewType.Hierarchy);
// 2019以降
property.SetRoot(frame, HierarchyFrameDataView.columnTotalTime, 0);
```

第2引数はEnumの指定方法が変わっただけですが、第3引数は [2018.4まで利用できたEnum](https://github.com/Unity-Technologies/UnityCsReference/blob/2018.4/Modules/ProfilerEditor/Public/ProfilerFrameDataView.bindings.cs#L15-L20) が [2019.1以降はinternalが指定されて参照できなくなっている](https://github.com/Unity-Technologies/UnityCsReference/blob/2019.1/Modules/ProfilerEditor/ProfilerWindow/IProfilerWindowController.cs#L9-L14) のがソースからも見て取れます。

別の例として、フレーム一覧取得の際に `GetNextFrameIndex` を使いましたが、たとえば最終フレームで呼び出した時の挙動は [2020.1での使用箇所](https://github.com/Unity-Technologies/UnityCsReference/blob/2020.1/Modules/ProfilerEditor/ProfilerWindow/ProfilerModules/CPU/ProfilerTimelineGUI.cs#L1701-L1703) を見る限り-1で判定するとよさそうです。

```C#
currentFrame = ProfilerDriver.GetNextFrameIndex(currentFrame);
if (currentFrame == -1)
    break;
```

なお、この数行だけで見ても 2020.2, 2021.1 で変更が入っており、開発の活発さが伺えます。

[2020.2](https://github.com/Unity-Technologies/UnityCsReference/blob/2020.3/Modules/ProfilerEditor/ProfilerWindow/ProfilerModules/CPU/ProfilerTimelineGUI.cs#L2081-L2083)

```C#
currentFrame = ProfilerDriver.GetNextFrameIndex(currentFrame);
if (currentFrame == k_InvalidOrCurrentFrameIndex || !ProfilerDriver.GetFramesBelongToSameSession(currentFrame, prevFrame))
    break;
```

[2021.1](https://github.com/Unity-Technologies/UnityCsReference/blob/2021.1/Modules/ProfilerEditor/ProfilerWindow/ProfilerModules/CPU/ProfilerTimelineGUI.cs#L2631-L2633)

```C#
currentFrame = ProfilerDriver.GetNextFrameIndex(currentFrame);
if (currentFrame == FrameDataView.invalidOrCurrentFrameIndex || !ProfilerDriver.GetFramesBelongToSameSession(currentFrame, prevFrame))
    break;
```

# まとめ
UnityのProfilerに表示された値を取り出し、それにまつわるUnityEditorInternalの中身を少し覗きました。
今回紹介した方法は、今後のUnityバージョンに合わせて変更する必要が出たりそもそも使えなくなる可能性もあるということを考慮した上で利用を検討しましょう。

# 参考記事

## UnityEditorInternal.ProfilerPropertyを使用する場合
https://qiita.com/KTA552/items/7d6abbd2224e04b77bde

https://qiita.com/t-matsunaga/items/b378387b4502a5a340c4

## Unityが書きだしたログファイルをそのまま読み込む場合
https://qiita.com/wotakuro/items/1375b882d9f6a9989b17