@mflrevan/ucp 0.4.3 → 0.4.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/bridge/com.ucp.bridge/CHANGELOG.md +145 -0
- package/bridge/com.ucp.bridge/CHANGELOG.md.meta +7 -0
- package/bridge/com.ucp.bridge/Editor/Bridge/BridgeServer.cs +583 -0
- package/bridge/com.ucp.bridge/Editor/Bridge/BridgeServer.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Bridge.meta +8 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/AssetController.cs +425 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/AssetController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/AssetImportSupport.cs +355 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/AssetImportSupport.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/BuildController.cs +233 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/BuildController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/CompilationController.cs +26 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/CompilationController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/EditorController.cs +31 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/EditorController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/EditorSettingsController.cs +527 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/EditorSettingsController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/FileController.cs +141 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/FileController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/HierarchyController.cs +326 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/HierarchyController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/ImporterController.cs +209 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/ImporterController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/LogsController.cs +409 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/LogsController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/MaterialController.cs +354 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/MaterialController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/ObjectReferenceResolver.cs +93 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/ObjectReferenceResolver.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/PackagesController.cs +503 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/PackagesController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/PlayModeController.cs +188 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/PlayModeController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/PrefabController.cs +260 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/PrefabController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/ProfilerController.cs +1679 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/ProfilerController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/PropertyController.cs +563 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/PropertyController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/SceneChangeTracker.cs +166 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/SceneChangeTracker.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/SceneController.cs +318 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/SceneController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/ScreenshotController.cs +125 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/ScreenshotController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/ScriptController.cs +104 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/ScriptController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/SnapshotController.cs +227 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/SnapshotController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/TestRunnerController.cs +240 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/TestRunnerController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/VcsController.cs +611 -0
- package/bridge/com.ucp.bridge/Editor/Controllers/VcsController.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Controllers.meta +8 -0
- package/bridge/com.ucp.bridge/Editor/Protocol/CommandRouter.cs +53 -0
- package/bridge/com.ucp.bridge/Editor/Protocol/CommandRouter.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Protocol/MessageTypes.cs +80 -0
- package/bridge/com.ucp.bridge/Editor/Protocol/MessageTypes.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Protocol/MiniJson.cs +358 -0
- package/bridge/com.ucp.bridge/Editor/Protocol/MiniJson.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Protocol.meta +8 -0
- package/bridge/com.ucp.bridge/Editor/Scripts/IUCPScript.cs +37 -0
- package/bridge/com.ucp.bridge/Editor/Scripts/IUCPScript.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Editor/Scripts.meta +8 -0
- package/bridge/com.ucp.bridge/Editor/UCP.Bridge.Editor.asmdef +16 -0
- package/bridge/com.ucp.bridge/Editor/UCP.Bridge.Editor.asmdef.meta +7 -0
- package/bridge/com.ucp.bridge/Editor.meta +8 -0
- package/bridge/com.ucp.bridge/Runtime/UCP.Bridge.Runtime.asmdef +14 -0
- package/bridge/com.ucp.bridge/Runtime/UCP.Bridge.Runtime.asmdef.meta +7 -0
- package/bridge/com.ucp.bridge/Runtime.meta +8 -0
- package/bridge/com.ucp.bridge/Tests/Editor/ControllerSmokeTests.cs +1085 -0
- package/bridge/com.ucp.bridge/Tests/Editor/ControllerSmokeTests.cs.meta +2 -0
- package/bridge/com.ucp.bridge/Tests/Editor/UCP.Bridge.Editor.Tests.asmdef +12 -0
- package/bridge/com.ucp.bridge/Tests/Editor/UCP.Bridge.Editor.Tests.asmdef.meta +7 -0
- package/bridge/com.ucp.bridge/Tests/Editor.meta +8 -0
- package/bridge/com.ucp.bridge/Tests.meta +8 -0
- package/bridge/com.ucp.bridge/package.json +27 -0
- package/bridge/com.ucp.bridge/package.json.meta +7 -0
- package/package.json +2 -2
- package/scripts/install.js +4 -6
|
@@ -0,0 +1,1679 @@
|
|
|
1
|
+
using System;
|
|
2
|
+
using System.Collections.Generic;
|
|
3
|
+
using System.IO;
|
|
4
|
+
using System.Linq;
|
|
5
|
+
using System.Reflection;
|
|
6
|
+
using UnityEditor;
|
|
7
|
+
using Unity.Profiling;
|
|
8
|
+
using UnityEditor.Profiling;
|
|
9
|
+
using UnityEngine;
|
|
10
|
+
using UnityEngine.Profiling;
|
|
11
|
+
|
|
12
|
+
namespace UCP.Bridge
|
|
13
|
+
{
|
|
14
|
+
public static class ProfilerController
|
|
15
|
+
{
|
|
16
|
+
private const int DefaultFrameListLimit = 20;
|
|
17
|
+
private const int DefaultSummaryFrameWindow = 120;
|
|
18
|
+
private const int DefaultJsonExportFrameWindow = 120;
|
|
19
|
+
private const int MaxThreadProbeCount = 128;
|
|
20
|
+
private const long MinimumProfilerMemoryBytes = 16L * 1024L * 1024L;
|
|
21
|
+
private const long DefaultProfilerMemoryBytes = 128L * 1024L * 1024L;
|
|
22
|
+
private const long HeavyProfilerMemoryBytes = 64L * 1024L * 1024L;
|
|
23
|
+
private const long AbsoluteProfilerMemoryBytes = 256L * 1024L * 1024L;
|
|
24
|
+
private const long AbsoluteHeavyProfilerMemoryBytes = 128L * 1024L * 1024L;
|
|
25
|
+
|
|
26
|
+
private static readonly Type ProfilerDriverType = Type.GetType("UnityEditorInternal.ProfilerDriver, UnityEditor");
|
|
27
|
+
private static readonly MethodInfo GetRawFrameDataViewMethod = FindDriverMethod(
|
|
28
|
+
"GetRawFrameDataView",
|
|
29
|
+
typeof(int),
|
|
30
|
+
typeof(int));
|
|
31
|
+
private static readonly MethodInfo GetHierarchyFrameDataViewMethod = FindDriverMethod(
|
|
32
|
+
"GetHierarchyFrameDataView",
|
|
33
|
+
typeof(int),
|
|
34
|
+
typeof(int),
|
|
35
|
+
typeof(HierarchyFrameDataView.ViewModes),
|
|
36
|
+
typeof(int),
|
|
37
|
+
typeof(bool));
|
|
38
|
+
private static readonly MethodInfo ClearAllFramesMethod = FindDriverMethod("ClearAllFrames");
|
|
39
|
+
private static readonly PropertyInfo FirstFrameIndexProperty = FindDriverProperty("firstFrameIndex");
|
|
40
|
+
private static readonly PropertyInfo LastFrameIndexProperty = FindDriverProperty("lastFrameIndex");
|
|
41
|
+
private static readonly PropertyInfo DriverEnabledProperty = FindDriverProperty("enabled");
|
|
42
|
+
private static readonly PropertyInfo ProfileEditorProperty = FindDriverProperty("profileEditor");
|
|
43
|
+
private static readonly PropertyInfo DeepProfilingProperty = FindDriverProperty("deepProfiling");
|
|
44
|
+
private static readonly MethodInfo GetCategoriesCountMethod = typeof(Profiler).GetMethod(
|
|
45
|
+
"GetCategoriesCount",
|
|
46
|
+
BindingFlags.Public | BindingFlags.Static,
|
|
47
|
+
null,
|
|
48
|
+
Type.EmptyTypes,
|
|
49
|
+
null);
|
|
50
|
+
private static readonly MethodInfo GetAllCategoriesMethod = typeof(Profiler).GetMethod(
|
|
51
|
+
"GetAllCategories",
|
|
52
|
+
BindingFlags.Public | BindingFlags.Static,
|
|
53
|
+
null,
|
|
54
|
+
new[] { typeof(ProfilerCategory[]) },
|
|
55
|
+
null);
|
|
56
|
+
private static readonly MethodInfo IsCategoryEnabledMethod = typeof(Profiler).GetMethod(
|
|
57
|
+
"IsCategoryEnabled",
|
|
58
|
+
BindingFlags.Public | BindingFlags.Static,
|
|
59
|
+
null,
|
|
60
|
+
new[] { typeof(ProfilerCategory) },
|
|
61
|
+
null);
|
|
62
|
+
private static readonly MethodInfo SetCategoryEnabledMethod = typeof(Profiler).GetMethod(
|
|
63
|
+
"SetCategoryEnabled",
|
|
64
|
+
BindingFlags.Public | BindingFlags.Static,
|
|
65
|
+
null,
|
|
66
|
+
new[] { typeof(ProfilerCategory), typeof(bool) },
|
|
67
|
+
null);
|
|
68
|
+
|
|
69
|
+
private static readonly ProfilerSessionState SessionState = new();
|
|
70
|
+
|
|
71
|
+
public static void Register(CommandRouter router)
|
|
72
|
+
{
|
|
73
|
+
router.Register("profiler/status", HandleStatus);
|
|
74
|
+
router.Register("profiler/config/get", HandleConfigGet);
|
|
75
|
+
router.Register("profiler/config/set", HandleConfigSet);
|
|
76
|
+
router.Register("profiler/session/start", HandleSessionStart);
|
|
77
|
+
router.Register("profiler/session/stop", HandleSessionStop);
|
|
78
|
+
router.Register("profiler/session/clear", HandleSessionClear);
|
|
79
|
+
router.Register("profiler/capture/save", HandleCaptureSave);
|
|
80
|
+
router.Register("profiler/capture/load", HandleCaptureLoad);
|
|
81
|
+
router.Register("profiler/frames/list", HandleFramesList);
|
|
82
|
+
router.Register("profiler/frames/show", HandleFrameShow);
|
|
83
|
+
router.Register("profiler/hierarchy", HandleHierarchy);
|
|
84
|
+
router.Register("profiler/timeline", HandleTimeline);
|
|
85
|
+
router.Register("profiler/callstacks", HandleCallstacks);
|
|
86
|
+
router.Register("profiler/summary", HandleSummary);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
private static object HandleStatus(string paramsJson)
|
|
90
|
+
{
|
|
91
|
+
return BuildStatusResponse(new List<string>());
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
private static object HandleConfigGet(string paramsJson)
|
|
95
|
+
{
|
|
96
|
+
return BuildStatusResponse(new List<string>());
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
private static object HandleConfigSet(string paramsJson)
|
|
100
|
+
{
|
|
101
|
+
var parameters = ParseParams(paramsJson);
|
|
102
|
+
var warnings = ApplyConfiguration(parameters, Profiler.enableBinaryLog, false);
|
|
103
|
+
return BuildStatusResponse(warnings);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
private static object HandleSessionStart(string paramsJson)
|
|
107
|
+
{
|
|
108
|
+
var parameters = ParseParams(paramsJson);
|
|
109
|
+
var warnings = new List<string>();
|
|
110
|
+
|
|
111
|
+
if (!SessionState.Active)
|
|
112
|
+
CaptureSessionDefaults();
|
|
113
|
+
|
|
114
|
+
var clearFirst = GetNullableBool(parameters, "clearFirst");
|
|
115
|
+
if (clearFirst.GetValueOrDefault() || (!clearFirst.HasValue && HasBufferedFrames()))
|
|
116
|
+
{
|
|
117
|
+
warnings.AddRange(ClearBufferedFrames());
|
|
118
|
+
if (!clearFirst.HasValue)
|
|
119
|
+
warnings.Add("Cleared existing buffered profiler frames before starting the new session to keep memory usage bounded.");
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
warnings.AddRange(ApplyConfiguration(parameters, false, true));
|
|
123
|
+
|
|
124
|
+
var requestedMode = GetString(parameters, "mode");
|
|
125
|
+
if (!string.IsNullOrEmpty(requestedMode))
|
|
126
|
+
{
|
|
127
|
+
SessionState.RequestedMode = NormalizeMode(requestedMode);
|
|
128
|
+
warnings.AddRange(ApplyRequestedMode(SessionState.RequestedMode));
|
|
129
|
+
}
|
|
130
|
+
else if (string.IsNullOrEmpty(SessionState.RequestedMode))
|
|
131
|
+
{
|
|
132
|
+
SessionState.RequestedMode = GetProfileEditor() ? "edit" : "play";
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
SessionState.Active = true;
|
|
136
|
+
SessionState.SessionId = Guid.NewGuid().ToString("N");
|
|
137
|
+
SessionState.StartedAtUtc = DateTime.UtcNow.ToString("o");
|
|
138
|
+
SessionState.StoppedAtUtc = null;
|
|
139
|
+
SessionState.LastCapturePath = NormalizePath(Profiler.logFile);
|
|
140
|
+
SessionState.Warnings = warnings.ToList();
|
|
141
|
+
|
|
142
|
+
SetDriverEnabled(true);
|
|
143
|
+
Profiler.enabled = true;
|
|
144
|
+
|
|
145
|
+
return BuildStatusResponse(warnings, "started");
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
private static object HandleSessionStop(string paramsJson)
|
|
149
|
+
{
|
|
150
|
+
var warnings = new List<string>();
|
|
151
|
+
SetDriverEnabled(false);
|
|
152
|
+
Profiler.enabled = false;
|
|
153
|
+
warnings.AddRange(RestoreSessionDefaults());
|
|
154
|
+
|
|
155
|
+
SessionState.Active = false;
|
|
156
|
+
SessionState.SessionId = null;
|
|
157
|
+
SessionState.StoppedAtUtc = DateTime.UtcNow.ToString("o");
|
|
158
|
+
SessionState.LastCapturePath = NormalizePath(Profiler.logFile);
|
|
159
|
+
if (SessionState.RequestedBinaryLog && !File.Exists(SessionState.LastCapturePath ?? string.Empty))
|
|
160
|
+
{
|
|
161
|
+
warnings.Add(
|
|
162
|
+
"Unity Editor does not emit live binary profiler logs at runtime. Use `ucp profiler capture save --output <file>.json` for a structured snapshot, or export raw/data captures manually from the Profiler window.");
|
|
163
|
+
}
|
|
164
|
+
SessionState.RequestedDeepProfile = false;
|
|
165
|
+
SessionState.RequestedAllocationCallstacks = false;
|
|
166
|
+
SessionState.RequestedBinaryLog = false;
|
|
167
|
+
SessionState.Warnings = warnings.ToList();
|
|
168
|
+
|
|
169
|
+
var response = BuildStatusResponse(warnings, "stopped");
|
|
170
|
+
var result = response as Dictionary<string, object>;
|
|
171
|
+
result["capture"] = BuildCaptureData(SessionState.LastCapturePath, "current");
|
|
172
|
+
return result;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
private static object HandleSessionClear(string paramsJson)
|
|
176
|
+
{
|
|
177
|
+
var warnings = ClearBufferedFrames();
|
|
178
|
+
var result = new Dictionary<string, object>
|
|
179
|
+
{
|
|
180
|
+
["status"] = "cleared",
|
|
181
|
+
["frames"] = BuildFrameRangeData(),
|
|
182
|
+
["warnings"] = warnings
|
|
183
|
+
};
|
|
184
|
+
return result;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
private static object HandleCaptureSave(string paramsJson)
|
|
188
|
+
{
|
|
189
|
+
var parameters = ParseParams(paramsJson);
|
|
190
|
+
var output = RequireString(parameters, "output");
|
|
191
|
+
var normalizedOutput = NormalizeAbsolutePath(output);
|
|
192
|
+
Directory.CreateDirectory(Path.GetDirectoryName(normalizedOutput));
|
|
193
|
+
|
|
194
|
+
var source = GetExistingCaptureSource();
|
|
195
|
+
if (string.IsNullOrEmpty(source))
|
|
196
|
+
{
|
|
197
|
+
if (string.Equals(Path.GetExtension(normalizedOutput), ".json", StringComparison.OrdinalIgnoreCase))
|
|
198
|
+
return ExportJsonCapture(normalizedOutput);
|
|
199
|
+
|
|
200
|
+
throw new ArgumentException(
|
|
201
|
+
"No raw/data profiler capture file is available. In the Unity Editor, use a .json output path for a structured snapshot export.");
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (!PathsEqual(source, normalizedOutput))
|
|
205
|
+
File.Copy(source, normalizedOutput, true);
|
|
206
|
+
|
|
207
|
+
return new Dictionary<string, object>
|
|
208
|
+
{
|
|
209
|
+
["capture"] = BuildCaptureData(normalizedOutput, "saved"),
|
|
210
|
+
["warnings"] = new List<object>()
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
private static object HandleCaptureLoad(string paramsJson)
|
|
215
|
+
{
|
|
216
|
+
var parameters = ParseParams(paramsJson);
|
|
217
|
+
var input = NormalizeAbsolutePath(RequireString(parameters, "input"));
|
|
218
|
+
if (!File.Exists(input))
|
|
219
|
+
throw new ArgumentException($"Profiler capture not found: {input}");
|
|
220
|
+
|
|
221
|
+
Profiler.AddFramesFromFile(input);
|
|
222
|
+
SessionState.LoadedCapturePath = input;
|
|
223
|
+
SessionState.LastCapturePath = input;
|
|
224
|
+
|
|
225
|
+
return new Dictionary<string, object>
|
|
226
|
+
{
|
|
227
|
+
["capture"] = BuildCaptureData(input, "loaded"),
|
|
228
|
+
["frames"] = BuildFrameRangeData(),
|
|
229
|
+
["warnings"] = new List<object>()
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
private static object HandleFramesList(string paramsJson)
|
|
234
|
+
{
|
|
235
|
+
var parameters = ParseParams(paramsJson);
|
|
236
|
+
var limit = Math.Max(1, GetInt(parameters, "limit", DefaultFrameListLimit));
|
|
237
|
+
if (!TryGetFrameRange(out var firstFrame, out var lastFrame))
|
|
238
|
+
{
|
|
239
|
+
return new Dictionary<string, object>
|
|
240
|
+
{
|
|
241
|
+
["frameRange"] = BuildFrameRangeData(),
|
|
242
|
+
["frames"] = new List<object>(),
|
|
243
|
+
["warnings"] = new List<object>()
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
var requestedFirst = GetNullableInt(parameters, "firstFrame") ?? firstFrame;
|
|
248
|
+
var requestedLast = GetNullableInt(parameters, "lastFrame") ?? lastFrame;
|
|
249
|
+
requestedFirst = Math.Max(firstFrame, requestedFirst);
|
|
250
|
+
requestedLast = Math.Min(lastFrame, requestedLast);
|
|
251
|
+
|
|
252
|
+
if (requestedFirst > requestedLast)
|
|
253
|
+
throw new ArgumentException("Requested frame range is empty");
|
|
254
|
+
|
|
255
|
+
var frameIndexes = Enumerable.Range(requestedFirst, requestedLast - requestedFirst + 1)
|
|
256
|
+
.Select(index => requestedLast - (index - requestedFirst))
|
|
257
|
+
.Take(limit)
|
|
258
|
+
.OrderBy(index => index)
|
|
259
|
+
.ToList();
|
|
260
|
+
|
|
261
|
+
var frames = new List<object>();
|
|
262
|
+
foreach (var frameIndex in frameIndexes)
|
|
263
|
+
frames.Add(BuildFrameSummary(frameIndex, false));
|
|
264
|
+
|
|
265
|
+
return new Dictionary<string, object>
|
|
266
|
+
{
|
|
267
|
+
["frameRange"] = new Dictionary<string, object>
|
|
268
|
+
{
|
|
269
|
+
["firstFrame"] = requestedFirst,
|
|
270
|
+
["lastFrame"] = requestedLast,
|
|
271
|
+
["returned"] = frames.Count
|
|
272
|
+
},
|
|
273
|
+
["frames"] = frames,
|
|
274
|
+
["warnings"] = new List<object>()
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
private static object HandleFrameShow(string paramsJson)
|
|
279
|
+
{
|
|
280
|
+
var parameters = ParseParams(paramsJson);
|
|
281
|
+
var frameIndex = ResolveFrame(parameters);
|
|
282
|
+
var includeThreads = GetBool(parameters, "includeThreads", false);
|
|
283
|
+
|
|
284
|
+
return new Dictionary<string, object>
|
|
285
|
+
{
|
|
286
|
+
["frame"] = BuildFrameSummary(frameIndex, includeThreads),
|
|
287
|
+
["warnings"] = new List<object>()
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
private static object HandleHierarchy(string paramsJson)
|
|
292
|
+
{
|
|
293
|
+
var parameters = ParseParams(paramsJson);
|
|
294
|
+
var frameIndex = ResolveFrame(parameters);
|
|
295
|
+
var threadIndex = GetInt(parameters, "thread", 0);
|
|
296
|
+
var limit = Math.Max(1, GetInt(parameters, "limit", 50));
|
|
297
|
+
var sort = GetString(parameters, "sort") ?? "total-time";
|
|
298
|
+
var maxDepth = GetNullableInt(parameters, "maxDepth");
|
|
299
|
+
|
|
300
|
+
using (var view = GetHierarchyFrameDataView(frameIndex, threadIndex))
|
|
301
|
+
{
|
|
302
|
+
if (view == null || !view.valid)
|
|
303
|
+
throw new ArgumentException($"Hierarchy profiler data is unavailable for frame {frameIndex}, thread {threadIndex}");
|
|
304
|
+
|
|
305
|
+
var items = CollectHierarchyItems(view, maxDepth);
|
|
306
|
+
items = SortHierarchyItems(items, sort);
|
|
307
|
+
|
|
308
|
+
var truncated = items.Count > limit;
|
|
309
|
+
if (truncated)
|
|
310
|
+
items = items.Take(limit).ToList();
|
|
311
|
+
|
|
312
|
+
return new Dictionary<string, object>
|
|
313
|
+
{
|
|
314
|
+
["frame"] = frameIndex,
|
|
315
|
+
["thread"] = threadIndex,
|
|
316
|
+
["sort"] = sort,
|
|
317
|
+
["count"] = items.Count,
|
|
318
|
+
["truncated"] = truncated,
|
|
319
|
+
["items"] = items.Select(item => (object)item.ToDictionary()).ToList(),
|
|
320
|
+
["warnings"] = new List<object>()
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
private static object HandleTimeline(string paramsJson)
|
|
326
|
+
{
|
|
327
|
+
var parameters = ParseParams(paramsJson);
|
|
328
|
+
var frameIndex = ResolveFrame(parameters);
|
|
329
|
+
var threadIndex = GetInt(parameters, "thread", 0);
|
|
330
|
+
var limit = Math.Max(1, GetInt(parameters, "limit", 200));
|
|
331
|
+
var maxDepth = GetNullableInt(parameters, "maxDepth");
|
|
332
|
+
var includeMetadata = GetBool(parameters, "includeMetadata", false);
|
|
333
|
+
|
|
334
|
+
using (var view = GetRawFrameDataView(frameIndex, threadIndex))
|
|
335
|
+
{
|
|
336
|
+
if (view == null || !view.valid)
|
|
337
|
+
throw new ArgumentException($"Raw profiler data is unavailable for frame {frameIndex}, thread {threadIndex}");
|
|
338
|
+
|
|
339
|
+
var samples = CollectTimelineSamples(view, maxDepth, includeMetadata);
|
|
340
|
+
var truncated = samples.Count > limit;
|
|
341
|
+
if (truncated)
|
|
342
|
+
samples = samples.Take(limit).ToList();
|
|
343
|
+
|
|
344
|
+
return new Dictionary<string, object>
|
|
345
|
+
{
|
|
346
|
+
["frame"] = frameIndex,
|
|
347
|
+
["thread"] = threadIndex,
|
|
348
|
+
["count"] = samples.Count,
|
|
349
|
+
["truncated"] = truncated,
|
|
350
|
+
["samples"] = samples.Cast<object>().ToList(),
|
|
351
|
+
["warnings"] = new List<object>()
|
|
352
|
+
};
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
private static object HandleCallstacks(string paramsJson)
|
|
357
|
+
{
|
|
358
|
+
var parameters = ParseParams(paramsJson);
|
|
359
|
+
var frameIndex = ResolveFrame(parameters);
|
|
360
|
+
var threadIndex = GetInt(parameters, "thread", 0);
|
|
361
|
+
var kind = (GetString(parameters, "kind") ?? "raw").ToLowerInvariant();
|
|
362
|
+
var resolveMethods = GetBool(parameters, "resolveMethods", false);
|
|
363
|
+
|
|
364
|
+
if (kind == "raw")
|
|
365
|
+
{
|
|
366
|
+
var sampleIndex = GetNullableInt(parameters, "sample");
|
|
367
|
+
if (!sampleIndex.HasValue)
|
|
368
|
+
throw new ArgumentException("Missing 'sample' for raw callstack lookup");
|
|
369
|
+
|
|
370
|
+
using (var view = GetRawFrameDataView(frameIndex, threadIndex))
|
|
371
|
+
{
|
|
372
|
+
if (view == null || !view.valid)
|
|
373
|
+
throw new ArgumentException($"Raw profiler data is unavailable for frame {frameIndex}, thread {threadIndex}");
|
|
374
|
+
|
|
375
|
+
var callstack = new List<ulong>();
|
|
376
|
+
view.GetSampleCallstack(sampleIndex.Value, callstack);
|
|
377
|
+
return BuildCallstackResponse(kind, frameIndex, threadIndex, sampleIndex.Value, null, callstack, resolveMethods, view);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if (kind == "hierarchy")
|
|
382
|
+
{
|
|
383
|
+
var itemId = GetNullableInt(parameters, "item");
|
|
384
|
+
if (!itemId.HasValue)
|
|
385
|
+
throw new ArgumentException("Missing 'item' for hierarchy callstack lookup");
|
|
386
|
+
|
|
387
|
+
using (var view = GetHierarchyFrameDataView(frameIndex, threadIndex))
|
|
388
|
+
{
|
|
389
|
+
if (view == null || !view.valid)
|
|
390
|
+
throw new ArgumentException($"Hierarchy profiler data is unavailable for frame {frameIndex}, thread {threadIndex}");
|
|
391
|
+
|
|
392
|
+
var callstack = new List<ulong>();
|
|
393
|
+
view.GetItemCallstack(itemId.Value, callstack);
|
|
394
|
+
return BuildCallstackResponse(kind, frameIndex, threadIndex, null, itemId.Value, callstack, resolveMethods, view);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
throw new ArgumentException($"Unsupported callstack kind: {kind}");
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
private static object HandleSummary(string paramsJson)
|
|
402
|
+
{
|
|
403
|
+
var parameters = ParseParams(paramsJson);
|
|
404
|
+
var limit = Math.Max(1, GetInt(parameters, "limit", 10));
|
|
405
|
+
var threadIndex = GetInt(parameters, "thread", 0);
|
|
406
|
+
|
|
407
|
+
if (!TryGetFrameRange(out var availableFirst, out var availableLast))
|
|
408
|
+
{
|
|
409
|
+
return new Dictionary<string, object>
|
|
410
|
+
{
|
|
411
|
+
["summary"] = new Dictionary<string, object>
|
|
412
|
+
{
|
|
413
|
+
["frameRange"] = BuildFrameRangeData(),
|
|
414
|
+
["stats"] = new Dictionary<string, object>(),
|
|
415
|
+
["topMarkers"] = new List<object>()
|
|
416
|
+
},
|
|
417
|
+
["warnings"] = new List<object>()
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
var requestedLastFrame = GetNullableInt(parameters, "lastFrame");
|
|
422
|
+
var lastFrame = Math.Min(availableLast, requestedLastFrame ?? availableLast);
|
|
423
|
+
var requestedFirstFrame = GetNullableInt(parameters, "firstFrame");
|
|
424
|
+
var firstFrame = Math.Max(
|
|
425
|
+
availableFirst,
|
|
426
|
+
requestedFirstFrame ?? Math.Max(availableFirst, lastFrame - DefaultSummaryFrameWindow + 1));
|
|
427
|
+
if (firstFrame > lastFrame)
|
|
428
|
+
throw new ArgumentException("Requested frame range is empty");
|
|
429
|
+
|
|
430
|
+
return new Dictionary<string, object>
|
|
431
|
+
{
|
|
432
|
+
["summary"] = BuildSummaryData(limit, threadIndex, firstFrame, lastFrame),
|
|
433
|
+
["warnings"] = new List<object>()
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
private static object BuildStatusResponse(List<string> warnings, string status = null)
|
|
438
|
+
{
|
|
439
|
+
var response = new Dictionary<string, object>();
|
|
440
|
+
if (!string.IsNullOrEmpty(status))
|
|
441
|
+
response["status"] = status;
|
|
442
|
+
|
|
443
|
+
response["session"] = BuildSessionData();
|
|
444
|
+
response["config"] = BuildConfigData();
|
|
445
|
+
response["capabilities"] = BuildCapabilitiesData();
|
|
446
|
+
response["frames"] = BuildFrameRangeData();
|
|
447
|
+
response["editorState"] = BuildEditorStateData();
|
|
448
|
+
response["warnings"] = warnings.Cast<object>().ToList();
|
|
449
|
+
return response;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
private static Dictionary<string, object> BuildSessionData()
|
|
453
|
+
{
|
|
454
|
+
return new Dictionary<string, object>
|
|
455
|
+
{
|
|
456
|
+
["active"] = SessionState.Active || Profiler.enabled,
|
|
457
|
+
["driverEnabled"] = GetDriverEnabled(),
|
|
458
|
+
["sessionId"] = SessionState.SessionId ?? string.Empty,
|
|
459
|
+
["requestedMode"] = string.IsNullOrEmpty(SessionState.RequestedMode)
|
|
460
|
+
? (GetProfileEditor() ? "edit" : "play")
|
|
461
|
+
: SessionState.RequestedMode,
|
|
462
|
+
["effectiveMode"] = GetProfileEditor() ? "edit" : "play",
|
|
463
|
+
["deepProfileRequested"] = SessionState.RequestedDeepProfile,
|
|
464
|
+
["deepProfileEffective"] = GetDeepProfiling(),
|
|
465
|
+
["allocationCallstacksRequested"] = SessionState.RequestedAllocationCallstacks,
|
|
466
|
+
["allocationCallstacksEffective"] = Profiler.enableAllocationCallstacks,
|
|
467
|
+
["binaryLog"] = Profiler.enableBinaryLog,
|
|
468
|
+
["outputPath"] = NormalizePath(Profiler.logFile) ?? string.Empty,
|
|
469
|
+
["loadedCapturePath"] = SessionState.LoadedCapturePath ?? string.Empty,
|
|
470
|
+
["lastCapturePath"] = SessionState.LastCapturePath ?? string.Empty,
|
|
471
|
+
["startedAtUtc"] = SessionState.StartedAtUtc ?? string.Empty,
|
|
472
|
+
["stoppedAtUtc"] = SessionState.StoppedAtUtc ?? string.Empty
|
|
473
|
+
};
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
private static Dictionary<string, object> BuildConfigData()
|
|
477
|
+
{
|
|
478
|
+
var availableCategories = GetAvailableCategories();
|
|
479
|
+
var enabledCategories = availableCategories
|
|
480
|
+
.Where(IsCategoryEnabled)
|
|
481
|
+
.Select(category => category.Name)
|
|
482
|
+
.OrderBy(name => name, StringComparer.OrdinalIgnoreCase)
|
|
483
|
+
.Cast<object>()
|
|
484
|
+
.ToList();
|
|
485
|
+
|
|
486
|
+
return new Dictionary<string, object>
|
|
487
|
+
{
|
|
488
|
+
["mode"] = GetProfileEditor() ? "edit" : "play",
|
|
489
|
+
["profileEditor"] = GetProfileEditor(),
|
|
490
|
+
["driverEnabled"] = GetDriverEnabled(),
|
|
491
|
+
["deepProfile"] = GetDeepProfiling(),
|
|
492
|
+
["allocationCallstacks"] = Profiler.enableAllocationCallstacks,
|
|
493
|
+
["binaryLog"] = Profiler.enableBinaryLog,
|
|
494
|
+
["outputPath"] = NormalizePath(Profiler.logFile) ?? string.Empty,
|
|
495
|
+
["maxUsedMemory"] = Convert.ToInt64(Profiler.maxUsedMemory),
|
|
496
|
+
["availableCategories"] = availableCategories
|
|
497
|
+
.Select(category => category.Name)
|
|
498
|
+
.OrderBy(name => name, StringComparer.OrdinalIgnoreCase)
|
|
499
|
+
.Cast<object>()
|
|
500
|
+
.ToList(),
|
|
501
|
+
["enabledCategories"] = enabledCategories
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
private static Dictionary<string, object> BuildCapabilitiesData()
|
|
506
|
+
{
|
|
507
|
+
return new Dictionary<string, object>
|
|
508
|
+
{
|
|
509
|
+
["status"] = true,
|
|
510
|
+
["config"] = true,
|
|
511
|
+
["sessionControl"] = true,
|
|
512
|
+
["driverRecordingControl"] = DriverEnabledProperty != null,
|
|
513
|
+
["binaryCapture"] = false,
|
|
514
|
+
["captureSaveFromEditor"] = false,
|
|
515
|
+
["captureLoad"] = true,
|
|
516
|
+
["categoryControl"] = true,
|
|
517
|
+
["rawFrameAccess"] = GetRawFrameDataViewMethod != null,
|
|
518
|
+
["hierarchyFrameAccess"] = GetHierarchyFrameDataViewMethod != null,
|
|
519
|
+
["clearFrames"] = ClearAllFramesMethod != null,
|
|
520
|
+
["editModeTargeting"] = ProfileEditorProperty != null,
|
|
521
|
+
["deepProfilingAutomation"] = DeepProfilingProperty != null,
|
|
522
|
+
["callstackResolution"] = GetRawFrameDataViewMethod != null,
|
|
523
|
+
["moduleCategorySelection"] = true,
|
|
524
|
+
["moduleLayoutAutomation"] = false,
|
|
525
|
+
["structuredSnapshotExport"] = true
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
private static Dictionary<string, object> BuildFrameRangeData()
|
|
530
|
+
{
|
|
531
|
+
var hasFrames = TryGetFrameRange(out var firstFrame, out var lastFrame);
|
|
532
|
+
return new Dictionary<string, object>
|
|
533
|
+
{
|
|
534
|
+
["count"] = hasFrames ? (lastFrame - firstFrame + 1) : 0,
|
|
535
|
+
["firstFrame"] = hasFrames ? firstFrame : -1,
|
|
536
|
+
["lastFrame"] = hasFrames ? lastFrame : -1
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
private static Dictionary<string, object> BuildEditorStateData()
|
|
541
|
+
{
|
|
542
|
+
return new Dictionary<string, object>
|
|
543
|
+
{
|
|
544
|
+
["playing"] = EditorApplication.isPlaying,
|
|
545
|
+
["paused"] = EditorApplication.isPaused,
|
|
546
|
+
["willChange"] = EditorApplication.isPlayingOrWillChangePlaymode,
|
|
547
|
+
["compiling"] = EditorApplication.isCompiling
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
private static Dictionary<string, object> BuildCaptureData(string path, string status)
|
|
552
|
+
{
|
|
553
|
+
var normalizedPath = NormalizePath(path) ?? string.Empty;
|
|
554
|
+
var exists = !string.IsNullOrEmpty(path) && File.Exists(path);
|
|
555
|
+
var extension = Path.GetExtension(normalizedPath)?.ToLowerInvariant() ?? string.Empty;
|
|
556
|
+
|
|
557
|
+
return new Dictionary<string, object>
|
|
558
|
+
{
|
|
559
|
+
["status"] = status,
|
|
560
|
+
["path"] = normalizedPath,
|
|
561
|
+
["exists"] = exists,
|
|
562
|
+
["sizeBytes"] = exists ? new FileInfo(path).Length : 0L,
|
|
563
|
+
["kind"] = extension.TrimStart('.'),
|
|
564
|
+
["frames"] = BuildFrameRangeData()
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
private static Dictionary<string, object> BuildFrameSummary(int frameIndex, bool includeThreads)
|
|
569
|
+
{
|
|
570
|
+
using (var mainThread = GetRawFrameDataView(frameIndex, 0))
|
|
571
|
+
{
|
|
572
|
+
if (mainThread == null || !mainThread.valid)
|
|
573
|
+
throw new ArgumentException($"Profiler frame {frameIndex} is unavailable");
|
|
574
|
+
|
|
575
|
+
var result = new Dictionary<string, object>
|
|
576
|
+
{
|
|
577
|
+
["frame"] = frameIndex,
|
|
578
|
+
["cpuMs"] = mainThread.frameTimeMs,
|
|
579
|
+
["gpuMs"] = mainThread.frameGpuTimeMs,
|
|
580
|
+
["fps"] = mainThread.frameFps,
|
|
581
|
+
["threadCount"] = CountThreads(frameIndex),
|
|
582
|
+
["gcAllocBytes"] = GetGcAllocBytes(frameIndex)
|
|
583
|
+
};
|
|
584
|
+
|
|
585
|
+
if (includeThreads)
|
|
586
|
+
{
|
|
587
|
+
var threads = new List<object>();
|
|
588
|
+
for (var threadIndex = 0; threadIndex < MaxThreadProbeCount; threadIndex++)
|
|
589
|
+
{
|
|
590
|
+
using (var threadView = GetRawFrameDataView(frameIndex, threadIndex))
|
|
591
|
+
{
|
|
592
|
+
if (threadView == null || !threadView.valid)
|
|
593
|
+
break;
|
|
594
|
+
|
|
595
|
+
threads.Add(new Dictionary<string, object>
|
|
596
|
+
{
|
|
597
|
+
["thread"] = threadIndex,
|
|
598
|
+
["threadId"] = threadView.threadId,
|
|
599
|
+
["threadName"] = threadView.threadName ?? string.Empty,
|
|
600
|
+
["threadGroup"] = threadView.threadGroupName ?? string.Empty,
|
|
601
|
+
["sampleCount"] = threadView.sampleCount,
|
|
602
|
+
["maxDepth"] = threadView.maxDepth
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
result["threads"] = threads;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
return result;
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
private static List<HierarchyItemRecord> CollectHierarchyItems(HierarchyFrameDataView view, int? maxDepth)
|
|
615
|
+
{
|
|
616
|
+
var items = new List<HierarchyItemRecord>();
|
|
617
|
+
var buffer = new List<int>();
|
|
618
|
+
CollectHierarchyChildren(view, view.GetRootItemID(), buffer, items, maxDepth);
|
|
619
|
+
return items;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
private static void CollectHierarchyChildren(
|
|
623
|
+
HierarchyFrameDataView view,
|
|
624
|
+
int parentId,
|
|
625
|
+
List<int> buffer,
|
|
626
|
+
List<HierarchyItemRecord> results,
|
|
627
|
+
int? maxDepth)
|
|
628
|
+
{
|
|
629
|
+
buffer.Clear();
|
|
630
|
+
view.GetItemChildren(parentId, buffer);
|
|
631
|
+
foreach (var childId in buffer)
|
|
632
|
+
{
|
|
633
|
+
var depth = view.GetItemDepth(childId);
|
|
634
|
+
if (maxDepth.HasValue && depth > maxDepth.Value)
|
|
635
|
+
continue;
|
|
636
|
+
|
|
637
|
+
var childBuffer = new List<int>();
|
|
638
|
+
view.GetItemChildren(childId, childBuffer);
|
|
639
|
+
|
|
640
|
+
results.Add(new HierarchyItemRecord
|
|
641
|
+
{
|
|
642
|
+
ItemId = childId,
|
|
643
|
+
Name = view.GetItemName(childId),
|
|
644
|
+
Path = view.GetItemPath(childId),
|
|
645
|
+
Depth = depth,
|
|
646
|
+
TotalMs = view.GetItemColumnDataAsDouble(childId, HierarchyFrameDataView.columnTotalTime),
|
|
647
|
+
SelfMs = view.GetItemColumnDataAsDouble(childId, HierarchyFrameDataView.columnSelfTime),
|
|
648
|
+
Calls = Convert.ToInt64(view.GetItemColumnDataAsDouble(childId, HierarchyFrameDataView.columnCalls)),
|
|
649
|
+
GcMemory = view.GetItemColumnDataAsDouble(childId, HierarchyFrameDataView.columnGcMemory),
|
|
650
|
+
ChildCount = childBuffer.Count
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
CollectHierarchyChildren(view, childId, childBuffer, results, maxDepth);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
private static List<HierarchyItemRecord> SortHierarchyItems(List<HierarchyItemRecord> items, string sort)
|
|
658
|
+
{
|
|
659
|
+
switch ((sort ?? string.Empty).ToLowerInvariant())
|
|
660
|
+
{
|
|
661
|
+
case "self-time":
|
|
662
|
+
return items.OrderByDescending(item => item.SelfMs).ToList();
|
|
663
|
+
case "calls":
|
|
664
|
+
return items.OrderByDescending(item => item.Calls).ToList();
|
|
665
|
+
case "gc-memory":
|
|
666
|
+
return items.OrderByDescending(item => item.GcMemory).ToList();
|
|
667
|
+
case "name":
|
|
668
|
+
return items.OrderBy(item => item.Name, StringComparer.OrdinalIgnoreCase).ToList();
|
|
669
|
+
default:
|
|
670
|
+
return items.OrderByDescending(item => item.TotalMs).ToList();
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
private static List<Dictionary<string, object>> CollectTimelineSamples(
|
|
675
|
+
RawFrameDataView view,
|
|
676
|
+
int? maxDepth,
|
|
677
|
+
bool includeMetadata)
|
|
678
|
+
{
|
|
679
|
+
var samples = new List<Dictionary<string, object>>();
|
|
680
|
+
var stack = new Stack<double>();
|
|
681
|
+
|
|
682
|
+
for (var sampleIndex = 0; sampleIndex < view.sampleCount; sampleIndex++)
|
|
683
|
+
{
|
|
684
|
+
var startMs = view.GetSampleStartTimeMs(sampleIndex);
|
|
685
|
+
var durationMs = view.GetSampleTimeMs(sampleIndex);
|
|
686
|
+
while (stack.Count > 0 && startMs >= stack.Peek())
|
|
687
|
+
stack.Pop();
|
|
688
|
+
|
|
689
|
+
var depth = stack.Count;
|
|
690
|
+
var endMs = startMs + durationMs;
|
|
691
|
+
stack.Push(endMs);
|
|
692
|
+
|
|
693
|
+
if (maxDepth.HasValue && depth > maxDepth.Value)
|
|
694
|
+
continue;
|
|
695
|
+
|
|
696
|
+
var categoryIndex = view.GetSampleCategoryIndex(sampleIndex);
|
|
697
|
+
var entry = new Dictionary<string, object>
|
|
698
|
+
{
|
|
699
|
+
["sample"] = sampleIndex,
|
|
700
|
+
["name"] = view.GetSampleName(sampleIndex),
|
|
701
|
+
["category"] = ResolveCategoryName(view, categoryIndex),
|
|
702
|
+
["startMs"] = startMs,
|
|
703
|
+
["durationMs"] = durationMs,
|
|
704
|
+
["depth"] = depth,
|
|
705
|
+
["childCount"] = view.GetSampleChildrenCount(sampleIndex),
|
|
706
|
+
["metadataCount"] = view.GetSampleMetadataCount(sampleIndex)
|
|
707
|
+
};
|
|
708
|
+
|
|
709
|
+
if (includeMetadata)
|
|
710
|
+
entry["metadata"] = ReadSampleMetadata(view, sampleIndex);
|
|
711
|
+
|
|
712
|
+
samples.Add(entry);
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
return samples;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
private static object BuildCallstackResponse(
|
|
719
|
+
string kind,
|
|
720
|
+
int frameIndex,
|
|
721
|
+
int threadIndex,
|
|
722
|
+
int? sampleIndex,
|
|
723
|
+
int? itemId,
|
|
724
|
+
List<ulong> callstack,
|
|
725
|
+
bool resolveMethods,
|
|
726
|
+
FrameDataView view)
|
|
727
|
+
{
|
|
728
|
+
var frames = new List<object>();
|
|
729
|
+
foreach (var address in callstack)
|
|
730
|
+
{
|
|
731
|
+
var entry = new Dictionary<string, object>
|
|
732
|
+
{
|
|
733
|
+
["address"] = $"0x{address:X}"
|
|
734
|
+
};
|
|
735
|
+
|
|
736
|
+
if (resolveMethods)
|
|
737
|
+
entry["method"] = ResolveMethodInfo(view, address);
|
|
738
|
+
|
|
739
|
+
frames.Add(entry);
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
return new Dictionary<string, object>
|
|
743
|
+
{
|
|
744
|
+
["callstack"] = new Dictionary<string, object>
|
|
745
|
+
{
|
|
746
|
+
["kind"] = kind,
|
|
747
|
+
["frame"] = frameIndex,
|
|
748
|
+
["thread"] = threadIndex,
|
|
749
|
+
["sample"] = sampleIndex ?? -1,
|
|
750
|
+
["item"] = itemId ?? -1,
|
|
751
|
+
["count"] = frames.Count,
|
|
752
|
+
["frames"] = frames
|
|
753
|
+
},
|
|
754
|
+
["warnings"] = new List<object>()
|
|
755
|
+
};
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
private static Dictionary<string, object> ResolveMethodInfo(FrameDataView view, ulong address)
|
|
759
|
+
{
|
|
760
|
+
try
|
|
761
|
+
{
|
|
762
|
+
object method = view.ResolveMethodInfo(address);
|
|
763
|
+
if (method == null)
|
|
764
|
+
return new Dictionary<string, object>();
|
|
765
|
+
|
|
766
|
+
var methodType = method.GetType();
|
|
767
|
+
return new Dictionary<string, object>
|
|
768
|
+
{
|
|
769
|
+
["name"] = ReadMember(methodType, method, "methodName"),
|
|
770
|
+
["sourceFile"] = ReadMember(methodType, method, "sourceFileName"),
|
|
771
|
+
["sourceLine"] = ReadMember(methodType, method, "sourceFileLine")
|
|
772
|
+
};
|
|
773
|
+
}
|
|
774
|
+
catch
|
|
775
|
+
{
|
|
776
|
+
return new Dictionary<string, object>();
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
private static object ReadMember(Type type, object instance, string name)
|
|
781
|
+
{
|
|
782
|
+
var property = type.GetProperty(name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.IgnoreCase);
|
|
783
|
+
if (property != null)
|
|
784
|
+
return property.GetValue(instance) ?? string.Empty;
|
|
785
|
+
|
|
786
|
+
var field = type.GetField(name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.IgnoreCase);
|
|
787
|
+
return field != null ? field.GetValue(instance) ?? string.Empty : string.Empty;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
private static Dictionary<string, object> ReadSampleMetadata(RawFrameDataView view, int sampleIndex)
|
|
791
|
+
{
|
|
792
|
+
var metadata = new Dictionary<string, object>();
|
|
793
|
+
var count = view.GetSampleMetadataCount(sampleIndex);
|
|
794
|
+
for (var metadataIndex = 0; metadataIndex < count; metadataIndex++)
|
|
795
|
+
{
|
|
796
|
+
try
|
|
797
|
+
{
|
|
798
|
+
metadata[metadataIndex.ToString()] = view.GetSampleMetadataAsString(sampleIndex, metadataIndex);
|
|
799
|
+
}
|
|
800
|
+
catch
|
|
801
|
+
{
|
|
802
|
+
try
|
|
803
|
+
{
|
|
804
|
+
metadata[metadataIndex.ToString()] = view.GetSampleMetadataAsLong(sampleIndex, metadataIndex);
|
|
805
|
+
}
|
|
806
|
+
catch
|
|
807
|
+
{
|
|
808
|
+
metadata[metadataIndex.ToString()] = string.Empty;
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
return metadata;
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
private static string ResolveCategoryName(FrameDataView view, int categoryIndex)
|
|
817
|
+
{
|
|
818
|
+
try
|
|
819
|
+
{
|
|
820
|
+
object categoryInfo = view.GetCategoryInfo((ushort)categoryIndex);
|
|
821
|
+
if (categoryInfo == null)
|
|
822
|
+
return categoryIndex.ToString();
|
|
823
|
+
|
|
824
|
+
var type = categoryInfo.GetType();
|
|
825
|
+
var property = type.GetProperty("Name", BindingFlags.Public | BindingFlags.Instance)
|
|
826
|
+
?? type.GetProperty("name", BindingFlags.Public | BindingFlags.Instance);
|
|
827
|
+
if (property != null)
|
|
828
|
+
return property.GetValue(categoryInfo)?.ToString() ?? categoryIndex.ToString();
|
|
829
|
+
}
|
|
830
|
+
catch
|
|
831
|
+
{
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
return categoryIndex.ToString();
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
private static int CountThreads(int frameIndex)
|
|
838
|
+
{
|
|
839
|
+
var count = 0;
|
|
840
|
+
for (var threadIndex = 0; threadIndex < MaxThreadProbeCount; threadIndex++)
|
|
841
|
+
{
|
|
842
|
+
using (var view = GetRawFrameDataView(frameIndex, threadIndex))
|
|
843
|
+
{
|
|
844
|
+
if (view == null || !view.valid)
|
|
845
|
+
break;
|
|
846
|
+
count++;
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
return count;
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
private static long GetGcAllocBytes(int frameIndex)
|
|
854
|
+
{
|
|
855
|
+
var total = 0L;
|
|
856
|
+
var gcMarkerId = FrameDataView.invalidMarkerId;
|
|
857
|
+
|
|
858
|
+
for (var threadIndex = 0; threadIndex < MaxThreadProbeCount; threadIndex++)
|
|
859
|
+
{
|
|
860
|
+
using (var view = GetRawFrameDataView(frameIndex, threadIndex))
|
|
861
|
+
{
|
|
862
|
+
if (view == null || !view.valid)
|
|
863
|
+
break;
|
|
864
|
+
|
|
865
|
+
if (gcMarkerId == FrameDataView.invalidMarkerId)
|
|
866
|
+
{
|
|
867
|
+
gcMarkerId = view.GetMarkerId("GC.Alloc");
|
|
868
|
+
if (gcMarkerId == FrameDataView.invalidMarkerId)
|
|
869
|
+
continue;
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
for (var sampleIndex = 0; sampleIndex < view.sampleCount; sampleIndex++)
|
|
873
|
+
{
|
|
874
|
+
if (view.GetSampleMarkerId(sampleIndex) != gcMarkerId)
|
|
875
|
+
continue;
|
|
876
|
+
|
|
877
|
+
try
|
|
878
|
+
{
|
|
879
|
+
total += view.GetSampleMetadataAsLong(sampleIndex, 0);
|
|
880
|
+
}
|
|
881
|
+
catch
|
|
882
|
+
{
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
return total;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
private static List<string> ApplyConfiguration(
|
|
892
|
+
Dictionary<string, object> parameters,
|
|
893
|
+
bool defaultBinaryLog,
|
|
894
|
+
bool defaultOutputIfNeeded)
|
|
895
|
+
{
|
|
896
|
+
var warnings = new List<string>();
|
|
897
|
+
|
|
898
|
+
var requestedMode = GetString(parameters, "mode");
|
|
899
|
+
if (!string.IsNullOrEmpty(requestedMode))
|
|
900
|
+
{
|
|
901
|
+
requestedMode = NormalizeMode(requestedMode);
|
|
902
|
+
SessionState.RequestedMode = requestedMode;
|
|
903
|
+
warnings.AddRange(ApplyRequestedMode(requestedMode));
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
var requestedDeepProfile = GetNullableBool(parameters, "deepProfile");
|
|
907
|
+
if (requestedDeepProfile.HasValue)
|
|
908
|
+
{
|
|
909
|
+
SessionState.RequestedDeepProfile = requestedDeepProfile.Value;
|
|
910
|
+
if (!SetDeepProfiling(requestedDeepProfile.Value))
|
|
911
|
+
warnings.Add("Deep profiling automation is unavailable on this Unity version; the requested value was recorded but not applied.");
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
var allocationCallstacks = GetNullableBool(parameters, "allocationCallstacks");
|
|
915
|
+
if (allocationCallstacks.HasValue)
|
|
916
|
+
{
|
|
917
|
+
Profiler.enableAllocationCallstacks = allocationCallstacks.Value;
|
|
918
|
+
SessionState.RequestedAllocationCallstacks = allocationCallstacks.Value;
|
|
919
|
+
if (allocationCallstacks.Value)
|
|
920
|
+
warnings.Add("Allocation callstacks add noticeable profiler overhead and can trigger frame drops or heavy editor memory pressure during longer captures.");
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
var binaryLog = GetNullableBool(parameters, "binaryLog") ?? defaultBinaryLog;
|
|
924
|
+
SessionState.RequestedBinaryLog = binaryLog;
|
|
925
|
+
Profiler.enableBinaryLog = binaryLog;
|
|
926
|
+
if (binaryLog && !Profiler.enableBinaryLog)
|
|
927
|
+
{
|
|
928
|
+
warnings.Add(
|
|
929
|
+
"Unity Editor keeps Profiler.enableBinaryLog disabled at runtime. Raw file capture requires a player build or manual Profiler export.");
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
var output = GetString(parameters, "output");
|
|
933
|
+
if (string.IsNullOrEmpty(output) && binaryLog && defaultOutputIfNeeded)
|
|
934
|
+
output = BuildDefaultCapturePath();
|
|
935
|
+
|
|
936
|
+
if (!string.IsNullOrEmpty(output))
|
|
937
|
+
{
|
|
938
|
+
var normalizedOutput = NormalizeAbsolutePath(output);
|
|
939
|
+
Directory.CreateDirectory(Path.GetDirectoryName(normalizedOutput));
|
|
940
|
+
Profiler.logFile = normalizedOutput;
|
|
941
|
+
SessionState.LastCapturePath = normalizedOutput;
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
warnings.AddRange(ApplySafeMemoryBudget(parameters, requestedDeepProfile, allocationCallstacks));
|
|
945
|
+
|
|
946
|
+
foreach (var categoryName in GetStringList(parameters, "enableCategories"))
|
|
947
|
+
warnings.AddRange(SetCategoryEnabled(categoryName, true));
|
|
948
|
+
|
|
949
|
+
foreach (var categoryName in GetStringList(parameters, "disableCategories"))
|
|
950
|
+
warnings.AddRange(SetCategoryEnabled(categoryName, false));
|
|
951
|
+
|
|
952
|
+
return warnings;
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
private static List<string> ApplyRequestedMode(string mode)
|
|
956
|
+
{
|
|
957
|
+
var warnings = new List<string>();
|
|
958
|
+
var editMode = mode == "edit";
|
|
959
|
+
|
|
960
|
+
if (!SetProfileEditor(editMode))
|
|
961
|
+
warnings.Add("Edit/play target selection is unavailable on this Unity version; profiler mode remained unchanged.");
|
|
962
|
+
|
|
963
|
+
if (mode == "play" && !EditorApplication.isPlaying)
|
|
964
|
+
EditorApplication.isPlaying = true;
|
|
965
|
+
else if (mode == "edit" && EditorApplication.isPlaying)
|
|
966
|
+
EditorApplication.isPlaying = false;
|
|
967
|
+
|
|
968
|
+
return warnings;
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
private static List<string> SetCategoryEnabled(string categoryName, bool enabled)
|
|
972
|
+
{
|
|
973
|
+
var warnings = new List<string>();
|
|
974
|
+
var category = ResolveCategory(categoryName);
|
|
975
|
+
if (!category.HasValue)
|
|
976
|
+
{
|
|
977
|
+
warnings.Add($"Unknown profiler category: {categoryName}");
|
|
978
|
+
return warnings;
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
if (SetCategoryEnabledMethod == null)
|
|
982
|
+
{
|
|
983
|
+
warnings.Add("Profiler category toggling is unavailable on this Unity version.");
|
|
984
|
+
return warnings;
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
SetCategoryEnabledMethod.Invoke(null, new object[] { category.Value, enabled });
|
|
988
|
+
warnings.Add("Unity's open Profiler window can override category settings to match active charts.");
|
|
989
|
+
return warnings;
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
private static List<string> ClearBufferedFrames()
|
|
993
|
+
{
|
|
994
|
+
var warnings = new List<string>();
|
|
995
|
+
if (ClearAllFramesMethod == null)
|
|
996
|
+
{
|
|
997
|
+
warnings.Add("Clearing buffered profiler frames is unavailable on this Unity version.");
|
|
998
|
+
return warnings;
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
ClearAllFramesMethod.Invoke(null, null);
|
|
1002
|
+
return warnings;
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
private static RawFrameDataView GetRawFrameDataView(int frameIndex, int threadIndex)
|
|
1006
|
+
{
|
|
1007
|
+
if (GetRawFrameDataViewMethod == null)
|
|
1008
|
+
return null;
|
|
1009
|
+
|
|
1010
|
+
try
|
|
1011
|
+
{
|
|
1012
|
+
return GetRawFrameDataViewMethod.Invoke(null, new object[] { frameIndex, threadIndex }) as RawFrameDataView;
|
|
1013
|
+
}
|
|
1014
|
+
catch
|
|
1015
|
+
{
|
|
1016
|
+
return null;
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
|
|
1020
|
+
private static HierarchyFrameDataView GetHierarchyFrameDataView(int frameIndex, int threadIndex)
|
|
1021
|
+
{
|
|
1022
|
+
if (GetHierarchyFrameDataViewMethod == null)
|
|
1023
|
+
return null;
|
|
1024
|
+
|
|
1025
|
+
try
|
|
1026
|
+
{
|
|
1027
|
+
return GetHierarchyFrameDataViewMethod.Invoke(
|
|
1028
|
+
null,
|
|
1029
|
+
new object[]
|
|
1030
|
+
{
|
|
1031
|
+
frameIndex,
|
|
1032
|
+
threadIndex,
|
|
1033
|
+
HierarchyFrameDataView.ViewModes.Default,
|
|
1034
|
+
HierarchyFrameDataView.columnDontSort,
|
|
1035
|
+
false
|
|
1036
|
+
}) as HierarchyFrameDataView;
|
|
1037
|
+
}
|
|
1038
|
+
catch
|
|
1039
|
+
{
|
|
1040
|
+
return null;
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
private static bool TryGetFrameRange(out int firstFrame, out int lastFrame)
|
|
1045
|
+
{
|
|
1046
|
+
firstFrame = -1;
|
|
1047
|
+
lastFrame = -1;
|
|
1048
|
+
if (FirstFrameIndexProperty == null || LastFrameIndexProperty == null)
|
|
1049
|
+
return false;
|
|
1050
|
+
|
|
1051
|
+
try
|
|
1052
|
+
{
|
|
1053
|
+
firstFrame = Convert.ToInt32(FirstFrameIndexProperty.GetValue(null));
|
|
1054
|
+
lastFrame = Convert.ToInt32(LastFrameIndexProperty.GetValue(null));
|
|
1055
|
+
return lastFrame >= firstFrame && firstFrame >= 0;
|
|
1056
|
+
}
|
|
1057
|
+
catch
|
|
1058
|
+
{
|
|
1059
|
+
return false;
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
private static int ResolveFrame(Dictionary<string, object> parameters)
|
|
1064
|
+
{
|
|
1065
|
+
if (GetNullableInt(parameters, "frame").HasValue)
|
|
1066
|
+
return GetNullableInt(parameters, "frame").Value;
|
|
1067
|
+
|
|
1068
|
+
if (!TryGetFrameRange(out _, out var lastFrame))
|
|
1069
|
+
throw new ArgumentException("No profiler frames are available. Start a session or load a capture first.");
|
|
1070
|
+
|
|
1071
|
+
return lastFrame;
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
private static bool GetProfileEditor()
|
|
1075
|
+
{
|
|
1076
|
+
if (ProfileEditorProperty == null)
|
|
1077
|
+
return false;
|
|
1078
|
+
|
|
1079
|
+
try
|
|
1080
|
+
{
|
|
1081
|
+
return Convert.ToBoolean(ProfileEditorProperty.GetValue(null));
|
|
1082
|
+
}
|
|
1083
|
+
catch
|
|
1084
|
+
{
|
|
1085
|
+
return false;
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
private static bool GetDriverEnabled()
|
|
1090
|
+
{
|
|
1091
|
+
if (DriverEnabledProperty == null)
|
|
1092
|
+
return false;
|
|
1093
|
+
|
|
1094
|
+
try
|
|
1095
|
+
{
|
|
1096
|
+
return Convert.ToBoolean(DriverEnabledProperty.GetValue(null));
|
|
1097
|
+
}
|
|
1098
|
+
catch
|
|
1099
|
+
{
|
|
1100
|
+
return false;
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
private static bool SetDriverEnabled(bool value)
|
|
1105
|
+
{
|
|
1106
|
+
if (DriverEnabledProperty == null)
|
|
1107
|
+
return false;
|
|
1108
|
+
|
|
1109
|
+
try
|
|
1110
|
+
{
|
|
1111
|
+
DriverEnabledProperty.SetValue(null, value);
|
|
1112
|
+
return true;
|
|
1113
|
+
}
|
|
1114
|
+
catch
|
|
1115
|
+
{
|
|
1116
|
+
return false;
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
private static bool SetProfileEditor(bool value)
|
|
1121
|
+
{
|
|
1122
|
+
if (ProfileEditorProperty == null)
|
|
1123
|
+
return false;
|
|
1124
|
+
|
|
1125
|
+
try
|
|
1126
|
+
{
|
|
1127
|
+
ProfileEditorProperty.SetValue(null, value);
|
|
1128
|
+
return true;
|
|
1129
|
+
}
|
|
1130
|
+
catch
|
|
1131
|
+
{
|
|
1132
|
+
return false;
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
private static bool GetDeepProfiling()
|
|
1137
|
+
{
|
|
1138
|
+
if (DeepProfilingProperty == null)
|
|
1139
|
+
return false;
|
|
1140
|
+
|
|
1141
|
+
try
|
|
1142
|
+
{
|
|
1143
|
+
return Convert.ToBoolean(DeepProfilingProperty.GetValue(null));
|
|
1144
|
+
}
|
|
1145
|
+
catch
|
|
1146
|
+
{
|
|
1147
|
+
return false;
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
private static bool SetDeepProfiling(bool value)
|
|
1152
|
+
{
|
|
1153
|
+
if (DeepProfilingProperty == null)
|
|
1154
|
+
return false;
|
|
1155
|
+
|
|
1156
|
+
try
|
|
1157
|
+
{
|
|
1158
|
+
DeepProfilingProperty.SetValue(null, value);
|
|
1159
|
+
return true;
|
|
1160
|
+
}
|
|
1161
|
+
catch
|
|
1162
|
+
{
|
|
1163
|
+
return false;
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
private static string GetExistingCaptureSource()
|
|
1168
|
+
{
|
|
1169
|
+
var current = NormalizePath(Profiler.logFile);
|
|
1170
|
+
if (!string.IsNullOrEmpty(current) && File.Exists(current))
|
|
1171
|
+
return current;
|
|
1172
|
+
|
|
1173
|
+
if (!string.IsNullOrEmpty(SessionState.LastCapturePath) && File.Exists(SessionState.LastCapturePath))
|
|
1174
|
+
return SessionState.LastCapturePath;
|
|
1175
|
+
|
|
1176
|
+
if (!string.IsNullOrEmpty(SessionState.LoadedCapturePath) && File.Exists(SessionState.LoadedCapturePath))
|
|
1177
|
+
return SessionState.LoadedCapturePath;
|
|
1178
|
+
|
|
1179
|
+
return null;
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
private static string BuildDefaultCapturePath()
|
|
1183
|
+
{
|
|
1184
|
+
var projectRoot = Path.GetDirectoryName(Application.dataPath);
|
|
1185
|
+
var capturesDirectory = Path.Combine(projectRoot, "ProfilerCaptures");
|
|
1186
|
+
Directory.CreateDirectory(capturesDirectory);
|
|
1187
|
+
var fileName = $"ucp-profile-{DateTime.UtcNow:yyyyMMdd-HHmmss}.raw";
|
|
1188
|
+
return Path.Combine(capturesDirectory, fileName);
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
private static List<string> ApplySafeMemoryBudget(
|
|
1192
|
+
Dictionary<string, object> parameters,
|
|
1193
|
+
bool? requestedDeepProfile,
|
|
1194
|
+
bool? requestedAllocationCallstacks)
|
|
1195
|
+
{
|
|
1196
|
+
var warnings = new List<string>();
|
|
1197
|
+
var heavyCapture =
|
|
1198
|
+
(requestedDeepProfile ?? GetDeepProfiling()) ||
|
|
1199
|
+
(requestedAllocationCallstacks ?? Profiler.enableAllocationCallstacks);
|
|
1200
|
+
|
|
1201
|
+
var recommendedBudget = heavyCapture ? HeavyProfilerMemoryBytes : DefaultProfilerMemoryBytes;
|
|
1202
|
+
var hardCap = heavyCapture ? AbsoluteHeavyProfilerMemoryBytes : AbsoluteProfilerMemoryBytes;
|
|
1203
|
+
var currentBudget = Convert.ToInt64(Profiler.maxUsedMemory);
|
|
1204
|
+
var requestedBudget = GetNullableLong(parameters, "maxUsedMemory");
|
|
1205
|
+
|
|
1206
|
+
long effectiveBudget;
|
|
1207
|
+
if (requestedBudget.HasValue)
|
|
1208
|
+
{
|
|
1209
|
+
effectiveBudget = Math.Min(
|
|
1210
|
+
hardCap,
|
|
1211
|
+
Math.Max(MinimumProfilerMemoryBytes, requestedBudget.Value));
|
|
1212
|
+
|
|
1213
|
+
if (requestedBudget.Value != effectiveBudget)
|
|
1214
|
+
{
|
|
1215
|
+
warnings.Add(
|
|
1216
|
+
$"Profiler buffer memory was clamped to {effectiveBudget / (1024L * 1024L)} MiB to prevent editor memory bloat.");
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
else
|
|
1220
|
+
{
|
|
1221
|
+
effectiveBudget = Math.Min(currentBudget, recommendedBudget);
|
|
1222
|
+
if (effectiveBudget < MinimumProfilerMemoryBytes)
|
|
1223
|
+
effectiveBudget = MinimumProfilerMemoryBytes;
|
|
1224
|
+
|
|
1225
|
+
if (currentBudget != effectiveBudget)
|
|
1226
|
+
{
|
|
1227
|
+
warnings.Add(
|
|
1228
|
+
$"Profiler buffer memory was reduced to {effectiveBudget / (1024L * 1024L)} MiB for a safer live-editor session.");
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
Profiler.maxUsedMemory = Convert.ToInt32(
|
|
1233
|
+
Math.Min(int.MaxValue, Math.Max(MinimumProfilerMemoryBytes, effectiveBudget)));
|
|
1234
|
+
|
|
1235
|
+
return warnings;
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
private static bool HasBufferedFrames()
|
|
1239
|
+
{
|
|
1240
|
+
return TryGetFrameRange(out var firstFrame, out var lastFrame) && lastFrame >= firstFrame;
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
private static void CaptureSessionDefaults()
|
|
1244
|
+
{
|
|
1245
|
+
SessionState.HadCapturedDefaults = true;
|
|
1246
|
+
SessionState.PreviousProfileEditor = GetProfileEditor();
|
|
1247
|
+
SessionState.PreviousDeepProfiling = GetDeepProfiling();
|
|
1248
|
+
SessionState.PreviousAllocationCallstacks = Profiler.enableAllocationCallstacks;
|
|
1249
|
+
SessionState.PreviousBinaryLog = Profiler.enableBinaryLog;
|
|
1250
|
+
SessionState.PreviousMaxUsedMemory = Convert.ToInt64(Profiler.maxUsedMemory);
|
|
1251
|
+
SessionState.PreviousOutputPath = NormalizePath(Profiler.logFile);
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
private static List<string> RestoreSessionDefaults()
|
|
1255
|
+
{
|
|
1256
|
+
if (!SessionState.HadCapturedDefaults)
|
|
1257
|
+
return new List<string>();
|
|
1258
|
+
|
|
1259
|
+
var warnings = new List<string>();
|
|
1260
|
+
|
|
1261
|
+
Profiler.enableAllocationCallstacks = SessionState.PreviousAllocationCallstacks;
|
|
1262
|
+
Profiler.enableBinaryLog = SessionState.PreviousBinaryLog;
|
|
1263
|
+
Profiler.maxUsedMemory = Convert.ToInt32(
|
|
1264
|
+
Math.Min(int.MaxValue, Math.Max(0L, SessionState.PreviousMaxUsedMemory)));
|
|
1265
|
+
Profiler.logFile = SessionState.PreviousOutputPath ?? string.Empty;
|
|
1266
|
+
|
|
1267
|
+
if (!SetDeepProfiling(SessionState.PreviousDeepProfiling))
|
|
1268
|
+
warnings.Add("Deep profiling state could not be restored automatically on this Unity version.");
|
|
1269
|
+
|
|
1270
|
+
if (!SetProfileEditor(SessionState.PreviousProfileEditor))
|
|
1271
|
+
warnings.Add("Profiler edit/play targeting could not be restored automatically on this Unity version.");
|
|
1272
|
+
|
|
1273
|
+
SessionState.HadCapturedDefaults = false;
|
|
1274
|
+
return warnings;
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1277
|
+
private static Dictionary<string, object> ParseParams(string paramsJson)
|
|
1278
|
+
{
|
|
1279
|
+
if (string.IsNullOrWhiteSpace(paramsJson))
|
|
1280
|
+
return new Dictionary<string, object>();
|
|
1281
|
+
|
|
1282
|
+
return MiniJson.Deserialize(paramsJson) as Dictionary<string, object>
|
|
1283
|
+
?? new Dictionary<string, object>();
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
private static string RequireString(Dictionary<string, object> parameters, string key)
|
|
1287
|
+
{
|
|
1288
|
+
var value = GetString(parameters, key);
|
|
1289
|
+
if (string.IsNullOrWhiteSpace(value))
|
|
1290
|
+
throw new ArgumentException($"Missing '{key}' parameter");
|
|
1291
|
+
return value;
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1294
|
+
private static string GetString(Dictionary<string, object> parameters, string key)
|
|
1295
|
+
{
|
|
1296
|
+
return parameters.TryGetValue(key, out var value) ? value?.ToString() : null;
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
private static int GetInt(Dictionary<string, object> parameters, string key, int defaultValue)
|
|
1300
|
+
{
|
|
1301
|
+
return GetNullableInt(parameters, key) ?? defaultValue;
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
private static int? GetNullableInt(Dictionary<string, object> parameters, string key)
|
|
1305
|
+
{
|
|
1306
|
+
if (!parameters.TryGetValue(key, out var value) || value == null)
|
|
1307
|
+
return null;
|
|
1308
|
+
|
|
1309
|
+
return Convert.ToInt32(value);
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
private static long? GetNullableLong(Dictionary<string, object> parameters, string key)
|
|
1313
|
+
{
|
|
1314
|
+
if (!parameters.TryGetValue(key, out var value) || value == null)
|
|
1315
|
+
return null;
|
|
1316
|
+
|
|
1317
|
+
return Convert.ToInt64(value);
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
private static bool GetBool(Dictionary<string, object> parameters, string key, bool defaultValue)
|
|
1321
|
+
{
|
|
1322
|
+
return GetNullableBool(parameters, key) ?? defaultValue;
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
private static bool? GetNullableBool(Dictionary<string, object> parameters, string key)
|
|
1326
|
+
{
|
|
1327
|
+
if (!parameters.TryGetValue(key, out var value) || value == null)
|
|
1328
|
+
return null;
|
|
1329
|
+
|
|
1330
|
+
return Convert.ToBoolean(value);
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
private static List<string> GetStringList(Dictionary<string, object> parameters, string key)
|
|
1334
|
+
{
|
|
1335
|
+
if (!parameters.TryGetValue(key, out var value) || value == null)
|
|
1336
|
+
return new List<string>();
|
|
1337
|
+
|
|
1338
|
+
if (value is List<object> values)
|
|
1339
|
+
return values.Where(item => item != null).Select(item => item.ToString()).ToList();
|
|
1340
|
+
|
|
1341
|
+
return new List<string> { value.ToString() };
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
private static string NormalizeMode(string mode)
|
|
1345
|
+
{
|
|
1346
|
+
return string.Equals(mode, "edit", StringComparison.OrdinalIgnoreCase) ? "edit" : "play";
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
private static string NormalizeAbsolutePath(string path)
|
|
1350
|
+
{
|
|
1351
|
+
var projectRoot = Path.GetDirectoryName(Application.dataPath);
|
|
1352
|
+
return Path.GetFullPath(Path.IsPathRooted(path) ? path : Path.Combine(projectRoot, path));
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
private static string NormalizePath(string path)
|
|
1356
|
+
{
|
|
1357
|
+
return string.IsNullOrWhiteSpace(path) ? null : Path.GetFullPath(path);
|
|
1358
|
+
}
|
|
1359
|
+
|
|
1360
|
+
private static bool PathsEqual(string left, string right)
|
|
1361
|
+
{
|
|
1362
|
+
return string.Equals(
|
|
1363
|
+
NormalizePath(left),
|
|
1364
|
+
NormalizePath(right),
|
|
1365
|
+
StringComparison.OrdinalIgnoreCase);
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
private static Dictionary<string, object> BuildSummaryData(
|
|
1369
|
+
int limit,
|
|
1370
|
+
int threadIndex,
|
|
1371
|
+
int? requestedFirstFrame = null,
|
|
1372
|
+
int? requestedLastFrame = null)
|
|
1373
|
+
{
|
|
1374
|
+
if (!TryGetFrameRange(out var availableFirst, out var availableLast))
|
|
1375
|
+
{
|
|
1376
|
+
return new Dictionary<string, object>
|
|
1377
|
+
{
|
|
1378
|
+
["frameRange"] = BuildFrameRangeData(),
|
|
1379
|
+
["stats"] = new Dictionary<string, object>(),
|
|
1380
|
+
["topMarkers"] = new List<object>()
|
|
1381
|
+
};
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
var firstFrame = requestedFirstFrame ?? availableFirst;
|
|
1385
|
+
var lastFrame = requestedLastFrame ?? availableLast;
|
|
1386
|
+
|
|
1387
|
+
var frameCount = 0;
|
|
1388
|
+
var totalCpuMs = 0.0;
|
|
1389
|
+
var totalGpuMs = 0.0;
|
|
1390
|
+
var totalFps = 0.0;
|
|
1391
|
+
var totalGcAllocBytes = 0L;
|
|
1392
|
+
var minCpuMs = double.MaxValue;
|
|
1393
|
+
var maxCpuMs = 0.0;
|
|
1394
|
+
var minGpuMs = double.MaxValue;
|
|
1395
|
+
var maxGpuMs = 0.0;
|
|
1396
|
+
var markerTotals = new Dictionary<string, MarkerAggregate>(StringComparer.Ordinal);
|
|
1397
|
+
|
|
1398
|
+
for (var frameIndex = firstFrame; frameIndex <= lastFrame; frameIndex++)
|
|
1399
|
+
{
|
|
1400
|
+
using (var raw = GetRawFrameDataView(frameIndex, threadIndex))
|
|
1401
|
+
{
|
|
1402
|
+
if (raw == null || !raw.valid)
|
|
1403
|
+
continue;
|
|
1404
|
+
|
|
1405
|
+
frameCount++;
|
|
1406
|
+
totalCpuMs += raw.frameTimeMs;
|
|
1407
|
+
totalGpuMs += raw.frameGpuTimeMs;
|
|
1408
|
+
totalFps += raw.frameFps;
|
|
1409
|
+
minCpuMs = Math.Min(minCpuMs, raw.frameTimeMs);
|
|
1410
|
+
maxCpuMs = Math.Max(maxCpuMs, raw.frameTimeMs);
|
|
1411
|
+
minGpuMs = Math.Min(minGpuMs, raw.frameGpuTimeMs);
|
|
1412
|
+
maxGpuMs = Math.Max(maxGpuMs, raw.frameGpuTimeMs);
|
|
1413
|
+
totalGcAllocBytes += GetGcAllocBytes(frameIndex);
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
using (var hierarchy = GetHierarchyFrameDataView(frameIndex, threadIndex))
|
|
1417
|
+
{
|
|
1418
|
+
if (hierarchy == null || !hierarchy.valid)
|
|
1419
|
+
continue;
|
|
1420
|
+
|
|
1421
|
+
foreach (var item in CollectHierarchyItems(hierarchy, null))
|
|
1422
|
+
{
|
|
1423
|
+
if (!markerTotals.TryGetValue(item.Name, out var aggregate))
|
|
1424
|
+
aggregate = new MarkerAggregate();
|
|
1425
|
+
|
|
1426
|
+
aggregate.SelfMs += item.SelfMs;
|
|
1427
|
+
aggregate.TotalMs += item.TotalMs;
|
|
1428
|
+
aggregate.Calls += item.Calls;
|
|
1429
|
+
markerTotals[item.Name] = aggregate;
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
if (frameCount == 0)
|
|
1435
|
+
{
|
|
1436
|
+
return new Dictionary<string, object>
|
|
1437
|
+
{
|
|
1438
|
+
["frameRange"] = new Dictionary<string, object>
|
|
1439
|
+
{
|
|
1440
|
+
["firstFrame"] = firstFrame,
|
|
1441
|
+
["lastFrame"] = lastFrame
|
|
1442
|
+
},
|
|
1443
|
+
["stats"] = new Dictionary<string, object>(),
|
|
1444
|
+
["topMarkers"] = new List<object>()
|
|
1445
|
+
};
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
var topMarkers = markerTotals
|
|
1449
|
+
.OrderByDescending(entry => entry.Value.SelfMs)
|
|
1450
|
+
.Take(limit)
|
|
1451
|
+
.Select(entry => new Dictionary<string, object>
|
|
1452
|
+
{
|
|
1453
|
+
["name"] = entry.Key,
|
|
1454
|
+
["selfMs"] = entry.Value.SelfMs,
|
|
1455
|
+
["totalMs"] = entry.Value.TotalMs,
|
|
1456
|
+
["calls"] = entry.Value.Calls
|
|
1457
|
+
})
|
|
1458
|
+
.Cast<object>()
|
|
1459
|
+
.ToList();
|
|
1460
|
+
|
|
1461
|
+
return new Dictionary<string, object>
|
|
1462
|
+
{
|
|
1463
|
+
["frameRange"] = new Dictionary<string, object>
|
|
1464
|
+
{
|
|
1465
|
+
["firstFrame"] = firstFrame,
|
|
1466
|
+
["lastFrame"] = lastFrame
|
|
1467
|
+
},
|
|
1468
|
+
["stats"] = new Dictionary<string, object>
|
|
1469
|
+
{
|
|
1470
|
+
["frameCount"] = frameCount,
|
|
1471
|
+
["avgCpuMs"] = totalCpuMs / frameCount,
|
|
1472
|
+
["minCpuMs"] = minCpuMs,
|
|
1473
|
+
["maxCpuMs"] = maxCpuMs,
|
|
1474
|
+
["avgGpuMs"] = totalGpuMs / frameCount,
|
|
1475
|
+
["minGpuMs"] = minGpuMs == double.MaxValue ? 0.0 : minGpuMs,
|
|
1476
|
+
["maxGpuMs"] = maxGpuMs,
|
|
1477
|
+
["avgFps"] = totalFps / frameCount,
|
|
1478
|
+
["gcAllocBytes"] = totalGcAllocBytes
|
|
1479
|
+
},
|
|
1480
|
+
["topMarkers"] = topMarkers
|
|
1481
|
+
};
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
private static object ExportJsonCapture(string outputPath)
|
|
1485
|
+
{
|
|
1486
|
+
List<object> frames;
|
|
1487
|
+
Dictionary<string, object> exportedFrameRange;
|
|
1488
|
+
var warnings = BuildJsonExportWarnings(out frames, out exportedFrameRange);
|
|
1489
|
+
var export = new Dictionary<string, object>
|
|
1490
|
+
{
|
|
1491
|
+
["generatedAtUtc"] = DateTime.UtcNow.ToString("o"),
|
|
1492
|
+
["bufferedFrameRange"] = BuildFrameRangeData(),
|
|
1493
|
+
["exportedFrameRange"] = exportedFrameRange,
|
|
1494
|
+
["session"] = BuildSessionData(),
|
|
1495
|
+
["config"] = BuildConfigData(),
|
|
1496
|
+
["frames"] = frames,
|
|
1497
|
+
["summary"] = BuildSummaryData(
|
|
1498
|
+
10,
|
|
1499
|
+
0,
|
|
1500
|
+
Convert.ToInt32(exportedFrameRange["firstFrame"]),
|
|
1501
|
+
Convert.ToInt32(exportedFrameRange["lastFrame"]))
|
|
1502
|
+
};
|
|
1503
|
+
|
|
1504
|
+
File.WriteAllText(outputPath, MiniJson.Serialize(export));
|
|
1505
|
+
return new Dictionary<string, object>
|
|
1506
|
+
{
|
|
1507
|
+
["capture"] = BuildCaptureData(outputPath, "saved"),
|
|
1508
|
+
["warnings"] = warnings
|
|
1509
|
+
};
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
private static List<object> BuildAllFrameSummaries(int firstFrame, int lastFrame)
|
|
1513
|
+
{
|
|
1514
|
+
if (firstFrame > lastFrame)
|
|
1515
|
+
return new List<object>();
|
|
1516
|
+
|
|
1517
|
+
var frames = new List<object>();
|
|
1518
|
+
for (var frameIndex = firstFrame; frameIndex <= lastFrame; frameIndex++)
|
|
1519
|
+
frames.Add(BuildFrameSummary(frameIndex, false));
|
|
1520
|
+
|
|
1521
|
+
return frames;
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1524
|
+
private static List<object> BuildJsonExportWarnings(
|
|
1525
|
+
out List<object> frames,
|
|
1526
|
+
out Dictionary<string, object> exportedFrameRange)
|
|
1527
|
+
{
|
|
1528
|
+
frames = new List<object>();
|
|
1529
|
+
exportedFrameRange = new Dictionary<string, object>
|
|
1530
|
+
{
|
|
1531
|
+
["firstFrame"] = -1,
|
|
1532
|
+
["lastFrame"] = -1,
|
|
1533
|
+
["count"] = 0
|
|
1534
|
+
};
|
|
1535
|
+
|
|
1536
|
+
if (!TryGetFrameRange(out var bufferedFirstFrame, out var bufferedLastFrame))
|
|
1537
|
+
return new List<object>();
|
|
1538
|
+
|
|
1539
|
+
var exportFirstFrame = Math.Max(
|
|
1540
|
+
bufferedFirstFrame,
|
|
1541
|
+
bufferedLastFrame - DefaultJsonExportFrameWindow + 1);
|
|
1542
|
+
frames = BuildAllFrameSummaries(exportFirstFrame, bufferedLastFrame);
|
|
1543
|
+
exportedFrameRange = new Dictionary<string, object>
|
|
1544
|
+
{
|
|
1545
|
+
["firstFrame"] = exportFirstFrame,
|
|
1546
|
+
["lastFrame"] = bufferedLastFrame,
|
|
1547
|
+
["count"] = frames.Count
|
|
1548
|
+
};
|
|
1549
|
+
|
|
1550
|
+
if (exportFirstFrame == bufferedFirstFrame)
|
|
1551
|
+
return new List<object>();
|
|
1552
|
+
|
|
1553
|
+
return new List<object>
|
|
1554
|
+
{
|
|
1555
|
+
$"Structured snapshot export included the most recent {frames.Count} frames out of {bufferedLastFrame - bufferedFirstFrame + 1} buffered frames."
|
|
1556
|
+
};
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
private static ProfilerCategory? ResolveCategory(string categoryName)
|
|
1560
|
+
{
|
|
1561
|
+
foreach (var category in GetAvailableCategories())
|
|
1562
|
+
{
|
|
1563
|
+
if (string.Equals(category.Name, categoryName, StringComparison.OrdinalIgnoreCase))
|
|
1564
|
+
return category;
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
return null;
|
|
1568
|
+
}
|
|
1569
|
+
|
|
1570
|
+
private static ProfilerCategory[] GetAvailableCategories()
|
|
1571
|
+
{
|
|
1572
|
+
if (GetCategoriesCountMethod == null || GetAllCategoriesMethod == null)
|
|
1573
|
+
return Array.Empty<ProfilerCategory>();
|
|
1574
|
+
|
|
1575
|
+
var count = Convert.ToInt32(GetCategoriesCountMethod.Invoke(null, null));
|
|
1576
|
+
if (count <= 0)
|
|
1577
|
+
return Array.Empty<ProfilerCategory>();
|
|
1578
|
+
|
|
1579
|
+
var categories = new ProfilerCategory[count];
|
|
1580
|
+
GetAllCategoriesMethod.Invoke(null, new object[] { categories });
|
|
1581
|
+
return categories;
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
private static bool IsCategoryEnabled(ProfilerCategory category)
|
|
1585
|
+
{
|
|
1586
|
+
if (IsCategoryEnabledMethod == null)
|
|
1587
|
+
return false;
|
|
1588
|
+
|
|
1589
|
+
try
|
|
1590
|
+
{
|
|
1591
|
+
return Convert.ToBoolean(IsCategoryEnabledMethod.Invoke(null, new object[] { category }));
|
|
1592
|
+
}
|
|
1593
|
+
catch
|
|
1594
|
+
{
|
|
1595
|
+
return false;
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
private static PropertyInfo FindDriverProperty(string name)
|
|
1600
|
+
{
|
|
1601
|
+
return ProfilerDriverType?.GetProperty(
|
|
1602
|
+
name,
|
|
1603
|
+
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static);
|
|
1604
|
+
}
|
|
1605
|
+
|
|
1606
|
+
private static MethodInfo FindDriverMethod(string name, params Type[] parameterTypes)
|
|
1607
|
+
{
|
|
1608
|
+
return ProfilerDriverType?.GetMethod(
|
|
1609
|
+
name,
|
|
1610
|
+
BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static,
|
|
1611
|
+
null,
|
|
1612
|
+
parameterTypes,
|
|
1613
|
+
null);
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
private sealed class ProfilerSessionState
|
|
1617
|
+
{
|
|
1618
|
+
public bool Active;
|
|
1619
|
+
public string SessionId;
|
|
1620
|
+
public string RequestedMode;
|
|
1621
|
+
public bool RequestedDeepProfile;
|
|
1622
|
+
public bool RequestedAllocationCallstacks;
|
|
1623
|
+
public bool RequestedBinaryLog;
|
|
1624
|
+
public bool HadCapturedDefaults;
|
|
1625
|
+
public bool PreviousProfileEditor;
|
|
1626
|
+
public bool PreviousDeepProfiling;
|
|
1627
|
+
public bool PreviousAllocationCallstacks;
|
|
1628
|
+
public bool PreviousBinaryLog;
|
|
1629
|
+
public long PreviousMaxUsedMemory;
|
|
1630
|
+
public string PreviousOutputPath;
|
|
1631
|
+
public string StartedAtUtc;
|
|
1632
|
+
public string StoppedAtUtc;
|
|
1633
|
+
public string LastCapturePath;
|
|
1634
|
+
public string LoadedCapturePath;
|
|
1635
|
+
public List<string> Warnings = new();
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
private sealed class MarkerAggregate
|
|
1639
|
+
{
|
|
1640
|
+
public double SelfMs;
|
|
1641
|
+
public double TotalMs;
|
|
1642
|
+
public long Calls;
|
|
1643
|
+
}
|
|
1644
|
+
|
|
1645
|
+
private sealed class HierarchyItemRecord
|
|
1646
|
+
{
|
|
1647
|
+
public int ItemId;
|
|
1648
|
+
public string Name;
|
|
1649
|
+
public string Path;
|
|
1650
|
+
public int Depth;
|
|
1651
|
+
public double TotalMs;
|
|
1652
|
+
public double SelfMs;
|
|
1653
|
+
public long Calls;
|
|
1654
|
+
public double GcMemory;
|
|
1655
|
+
public int ChildCount;
|
|
1656
|
+
|
|
1657
|
+
public Dictionary<string, object> ToDictionary()
|
|
1658
|
+
{
|
|
1659
|
+
return new Dictionary<string, object>
|
|
1660
|
+
{
|
|
1661
|
+
["item"] = ItemId,
|
|
1662
|
+
["name"] = Name ?? string.Empty,
|
|
1663
|
+
["path"] = Path ?? string.Empty,
|
|
1664
|
+
["depth"] = Depth,
|
|
1665
|
+
["totalMs"] = TotalMs,
|
|
1666
|
+
["selfMs"] = SelfMs,
|
|
1667
|
+
["calls"] = Calls,
|
|
1668
|
+
["gcMemory"] = GcMemory,
|
|
1669
|
+
["childCount"] = ChildCount
|
|
1670
|
+
};
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
public static implicit operator Dictionary<string, object>(HierarchyItemRecord item)
|
|
1674
|
+
{
|
|
1675
|
+
return item.ToDictionary();
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
}
|