in resharper/resharper-unity/src/Unity.Rider/Common/CSharp/Daemon/Profiler/UnityProfilerDaemon.cs [34:359]
public class UnityProfilerDaemon(
ILazy<IUnityProfilerSnapshotDataProvider> snapshotDataProvider,
ISolution solution,
UnityProfilerInsightProvider codeInsightProvider,
IconHost myIconHost,
ILogger logger)
: CSharpDaemonStageBase
{
protected override bool IsSupported(IPsiSourceFile sourceFile)
{
if (!solution.HasUnityReference())
return false;
var projectFile = sourceFile.ToProjectFile();
var project = projectFile?.GetProject();
if (project != null && !project.IsUnityProject() && !project.IsMiscFilesProject())
return false;
return sourceFile.IsLanguageSupported<CSharpLanguage>();
}
protected override IDaemonStageProcess CreateProcess(IDaemonProcess process, IContextBoundSettingsStore settings,
DaemonProcessKind processKind, ICSharpFile file)
{
return new Process(file, solution, process, settings, snapshotDataProvider, codeInsightProvider, logger,
myIconHost);
}
private class Process(
ICSharpFile file,
ISolution solution,
IDaemonProcess process,
IContextBoundSettingsStore settingsStore,
ILazy<IUnityProfilerSnapshotDataProvider> snapshotDataProvider,
UnityProfilerInsightProvider codeInsightProvider,
ILogger logger,
IconHost myIconHost)
: IDaemonStageProcess
{
private static readonly HighlightingString ourMethodCallSingle =
new(Strings.UnityProfilerSnapshot_Single_Method_Highlighting_display_name,
Strings.UnityProfilerSnapshot_Single_Method_Highlighting_tooltip,
Strings.UnityProfilerSnapshot_Single_Method_Highlighting_moreinfo);
private static readonly HighlightingString ourMethodCallMultiple =
new(Strings.UnityProfilerSnapshot_Multiple_Method_Highlighting_display_name,
Strings.UnityProfilerSnapshot_Multiple_Method_Highlighting_tooltip,
Strings.UnityProfilerSnapshot_Multiple_Method_Highlighting_moreinfo);
private static readonly HighlightingString ourClassSingle =
new(Strings.UnityProfilerSnapshot_Single_Class_Highlighting_display_name,
Strings.UnityProfilerSnapshot_Single_Class_Highlighting_tooltip,
Strings.UnityProfilerSnapshot_Single_Class_Highlighting_moreinfo);
private static readonly HighlightingString ourClassMultiple =
new(Strings.UnityProfilerSnapshot_Multiple_Class_Highlighting_display_name,
Strings.UnityProfilerSnapshot_Multiple_Class_Highlighting_tooltip,
Strings.UnityProfilerSnapshot_Multiple_Class_Highlighting_moreinfo);
private static readonly HighlightingString ourInternalCall =
new(Strings.UnityProfilerSnapshot_Internal_Call_Highlighting_display_name,
Strings.UnityProfilerSnapshot_Internal_Call_Highlighting_tooltip,
Strings.UnityProfilerSnapshot_Internal_Call_Highlighting_moreinfo);
private readonly struct HighlightingString(string displayName, string tooltip, string moreText)
{
public readonly string DisplayName = displayName;
public readonly string Tooltip = tooltip;
public readonly string MoreText = moreText;
}
public void Execute(Action<DaemonStageResult> committer)
{
var textControl = solution.GetComponent<ITextControlManager>().LastFocusedTextControlPerClient
.ForCurrentClient();
var consumer = new FilteringHighlightingConsumer(DaemonProcess.SourceFile, file, settingsStore);
using var samples = PooledList<PooledSample>.GetInstance();
using var childrenSamples = PooledList<PooledSample>.GetInstance();
foreach (var classLikeDeclaration in file.Descendants<IClassLikeDeclaration>())
{
IList<PooledSample> readOnlyList = samples;
readOnlyList.Clear();
var classCLRName = GetCLRName(classLikeDeclaration);
snapshotDataProvider.Value.TryGetTypeSamples(classCLRName, ref readOnlyList);
if (readOnlyList.Count == 0)
continue;
CreateHighlightingForDeclaration(samples, textControl, consumer, classLikeDeclaration, codeInsightProvider, logger, solution);
foreach (var descendant in classLikeDeclaration.Descendants<ICSharpDeclaration>())
{
samples.Clear();
switch (descendant)
{
case IPropertyDeclaration propertyDeclaration:
{
foreach (var accessorDeclaration in propertyDeclaration.AccessorDeclarationsEnumerable)
{
using var accessorSamples = PooledList<PooledSample>.GetInstance();
IList<PooledSample> accessorListSamples = accessorSamples;
var clrName = GetCLRName(accessorDeclaration);
snapshotDataProvider.Value.TryGetSamplesByQualifiedName(clrName, ref accessorListSamples);
if (accessorListSamples.Count == 0)
continue;
CreateHighlightingForDeclaration(accessorSamples, textControl, consumer,
accessorDeclaration, codeInsightProvider, logger, solution);
ProcessInternalExpressions(childrenSamples, accessorListSamples, accessorDeclaration,
consumer);
//collect samples from getters and setters for the property
samples.AddRange(accessorListSamples);
}
if (readOnlyList.Count == 0)
continue;
CreateHighlightingForDeclaration(samples, textControl, consumer, descendant, codeInsightProvider, logger, solution);
continue;
}
case IMethodDeclaration:
{
var clrName = GetCLRName(descendant);
if (!snapshotDataProvider.Value.TryGetSamplesByQualifiedName(clrName, ref readOnlyList))
continue;
if (readOnlyList.Count == 0)
continue;
CreateHighlightingForDeclaration(samples, textControl, consumer, descendant, codeInsightProvider, logger, solution);
ProcessInternalExpressions(childrenSamples, readOnlyList, descendant, consumer);
continue;
}
}
}
}
committer(new DaemonStageResult(consumer.CollectHighlightings()));
}
private void ProcessInternalExpressions(PooledList<PooledSample> pooledChildrenSamples,
IList<PooledSample> parentSamplesList, ICSharpDeclaration declaration,
FilteringHighlightingConsumer consumer)
{
pooledChildrenSamples.Clear();
foreach (var sample in parentSamplesList)
{
foreach (var child in sample.Children)
{
pooledChildrenSamples.Add(child);
if (child.IsProfilerMarker)
pooledChildrenSamples.AddRange(child.Children);
}
}
if (pooledChildrenSamples.Count == 0)
return;
//If this sample has children - go into the declaration and find invocations
foreach (var sharpExpression in declaration.Descendants<ICSharpExpression>())
{
//process methods calls
if (sharpExpression is IInvocationExpression invocationExpression)
{
if (invocationExpression.IsProfilerBeginSampleMethod())
{
var beginSampleArgument = invocationExpression.Arguments.FirstOrDefault();
var name = beginSampleArgument?.Value?.GetText()?.Trim('"');
ExtractSamplesAndAddHighlighting(pooledChildrenSamples, sharpExpression, declaration,
codeInsightProvider, consumer, name ?? string.Empty, ourInternalCall);
continue;
}
var (declaredElement, substitution, resolveErrorType) =
invocationExpression.InvocationExpressionReference.Resolve();
if (declaredElement != null && resolveErrorType == ResolveErrorType.OK)
{
ExtractHighlightingInformation(pooledChildrenSamples, declaredElement, sharpExpression,
declaration,
codeInsightProvider, consumer);
}
continue;
}
//process property calls
if (sharpExpression is IReferenceExpression referenceExpression)
{
var (declaredElement, substitution) = referenceExpression.Reference.Resolve();
if (declaredElement is IProperty property)
{
ExtractHighlightingInformation(pooledChildrenSamples, property.Getter, sharpExpression,
declaration, codeInsightProvider, consumer);
ExtractHighlightingInformation(pooledChildrenSamples, property.Setter, sharpExpression,
declaration, codeInsightProvider, consumer);
}
}
}
}
private static string GetCLRName(ICSharpDeclaration declaration)
{
if (declaration is IClassLikeDeclaration classLikeDeclaration)
return classLikeDeclaration.CLRName;
var cSharpTypeDeclaration = declaration.GetContainingTypeDeclaration()?.CLRName;
var declarationDeclaredName = declaration.DeclaredName;
var clrName = StringUtil.Combine(cSharpTypeDeclaration, declarationDeclaredName);
return clrName;
}
private static void CreateHighlightingForDeclaration(IReadOnlyList<PooledSample> samples, ITextControl textControl,
FilteringHighlightingConsumer consumer, ICSharpDeclaration declaration, UnityProfilerInsightProvider insightProvider, ILogger logger, ISolution solution)
{
var info = declaration is IClassLikeDeclaration
? CalculateDeclarationText(samples, ourClassSingle, ourClassMultiple)
: CalculateDeclarationText(samples, ourMethodCallSingle, ourMethodCallMultiple);
var bulbMenuItems = new List<BulbMenuItem>();
using var pooledHashSet = PooledHashSet<PooledSample>.GetInstance();
foreach (var s in samples)
{
if (s.Parent == null)
continue;
pooledHashSet.Add(s.Parent);
}
foreach (var sample in pooledHashSet)
{
var bulb = new ShowProfilerCallsBulbAction(sample, logger);
var bulbMenuItem =
new IntentionAction(bulb, null, BulbMenuAnchors.FirstClassContextItems)
.ToBulbMenuItem(solution, textControl);
bulbMenuItems.Add(bulbMenuItem);
}
insightProvider.AddHighlighting(consumer, declaration, declaration.DeclaredElement,
info.DisplayName,
info.Tooltip,
info.MoreText,
null, bulbMenuItems, new());
}
private readonly struct FunctionDeclarationSnapshotInfo(string displayName, string tooltip, string moreText)
{
public readonly string DisplayName = displayName;
public readonly string Tooltip = tooltip;
public readonly string MoreText = moreText;
}
private static FunctionDeclarationSnapshotInfo CalculateDeclarationText(IReadOnlyList<PooledSample> samples,
HighlightingString single, HighlightingString multiple)
{
var totalDuration = samples.Sum(s => s.Duration);
var totalPercentage = samples.Sum(s => s.FramePercentage);
var totalAllocation = StringUtil.StrFormatByteSize(samples.Sum(s => s.MemoryAllocation));
if (samples.Count == 1)
{
return new FunctionDeclarationSnapshotInfo(
string.Format(single.DisplayName, totalDuration, totalPercentage, totalAllocation),
string.Format(single.Tooltip, totalDuration, totalPercentage, totalAllocation),
string.Format(single.MoreText, totalDuration, totalPercentage, totalAllocation));
}
var min = samples.Min(s => s.Duration);
var max = samples.Max(s => s.Duration);
var avg = samples.Average(s => s.Duration);
return new(
string.Format(multiple.DisplayName, totalDuration, totalPercentage, samples.Count, totalAllocation),
string.Format(multiple.Tooltip, totalDuration, totalPercentage, min, max, avg, totalAllocation, samples.Count),
string.Format(multiple.MoreText, totalDuration, totalPercentage, min, max, avg, totalAllocation, samples.Count)
);
}
private void ExtractHighlightingInformation(List<PooledSample> children,
IDeclaredElement declaredElement, ICSharpExpression sharpExpression, ICSharpDeclaration declaration,
UnityProfilerInsightProvider insightProvider, FilteringHighlightingConsumer consumer)
{
if (declaredElement is not IClrDeclaredElement clrDeclaredElement)
return;
var qualifiedName =
$"{clrDeclaredElement.GetContainingType()?.GetClrName()}.{clrDeclaredElement.ShortName}";
ExtractSamplesAndAddHighlighting(children, sharpExpression, declaration, insightProvider, consumer,
qualifiedName, ourInternalCall);
}
private static void ExtractSamplesAndAddHighlighting(List<PooledSample> children,
ICSharpExpression sharpExpression,
ICSharpDeclaration declaration, UnityProfilerInsightProvider insightProvider,
FilteringHighlightingConsumer consumer, string qualifiedName, HighlightingString highlightingString)
{
using var samples = PooledList<PooledSample>.GetInstance();
foreach (var child in children)
{
if (child.QualifiedName.Equals(qualifiedName))
samples.Add(child);
}
if (samples.Count == 0)
return;
var durationSum = samples.Sum(s => s.Duration);
var percentageSum = samples.Sum(s => s.FramePercentage);
var memory = StringUtil.StrFormatByteSize(samples.Sum(s => s.MemoryAllocation));
var displayName = string.Format(highlightingString.DisplayName, durationSum, percentageSum, samples.Count, memory);
var tooltip = string.Format(highlightingString.Tooltip, qualifiedName, durationSum, percentageSum, memory,
samples.Count);
var moreText = string.Format(highlightingString.MoreText, qualifiedName, durationSum, percentageSum, memory,
samples.Count);
insightProvider.AddHighlighting(consumer, sharpExpression.GetDocumentRange(), declaration.DeclaredElement,
displayName, tooltip, moreText, null
, EmptyList<BulbMenuItem>.Instance, []);
}
public IDaemonProcess DaemonProcess { get; } = process;
}
}