Codeunit 1923 Profiling Data Processor, source in 29
src/System Application/App/Performance Profiler/src/ProfilingDataProcessor.Codeunit.al255 lines, Copyright (c) Microsoft Corporation. MIT
// ------------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
// ------------------------------------------------------------------------------------------------
namespace System.Tooling;
using System;
using System.Environment;
/// <summary>
/// The object providing functionality for processing perfrormance profiling data.
/// </summary>
codeunit 1923 "Profiling Data Processor"
{
Access = Internal;
SingleInstance = true;
InherentEntitlements = X;
InherentPermissions = X;
var
TempRawProfilingNodes: Record "Profiling Node";
TempCallTreeProfilingNodes: Record "Profiling Node";
/// <summary>
/// Initializes the profiling data processor with the profiling data.
/// </summary>
/// <param name="RawProfilingNode">The performance profiling data.</param>
/// <remarks>The data processor will reference the provided data, not copy it.</remarks>
procedure Initialize(var RawProfilingNode: Record "Profiling Node"; var CallTreeProfilingNode: Record "Profiling Node")
begin
TempRawProfilingNodes.Copy(RawProfilingNode, true);
TempCallTreeProfilingNodes.Copy(CallTreeProfilingNode, true);
end;
/// <summary>
/// Clears the previously initialized performance profiling data.
/// </summary>
procedure ClearData();
begin
TempRawProfilingNodes.DeleteAll();
TempCallTreeProfilingNodes.DeleteAll();
end;
/// <summary>
/// Checks if the profiling data processor is initialized with <see cref="Initialize"/> method.
/// </summary>
/// <returns>True, if the profiling data processor is initialized, false otherwise.</returns>
procedure IsInitialized(): Boolean
begin
if TempRawProfilingNodes.IsEmpty() then
exit(false);
if TempCallTreeProfilingNodes.IsEmpty() then
exit(false);
TempRawProfilingNodes.CalcSums("Self Time");
if TempRawProfilingNodes."Self Time" = 0 then
exit(false);
exit(true);
end;
/// <summary>
/// Gets aggregate self time of profiling nodes using the provided aggregation type.
/// </summary>
/// <param name="AggregatedProfilingNode">The resulting aggregation.</param>
/// <param name="ProfilingAggregationType">The value of what to aggregate self time by.</param>
procedure GetSelfTimeAggregate(var AggregatedProfilingNode: Record "Profiling Node"; ProfilingAggregationType: Enum "Profiling Aggregation Type")
begin
GetSelfTimeAggregate(AggregatedProfilingNode, ProfilingAggregationType, '');
end;
/// <summary>
/// Gets aggregate self time of profiling nodes using the provided aggregation type.
/// </summary>
/// <param name="AggregatedProfilingNode">The resulting aggregation.</param>
/// <param name="ProfilingAggregationType">The value of what to aggregate self time by.</param>
/// <param name="FilterText">The table view to indicate which profiling nodes should be included in aggregation.</param>
procedure GetSelfTimeAggregate(var AggregatedProfilingNode: Record "Profiling Node"; ProfilingAggregationType: Enum "Profiling Aggregation Type"; FilterText: Text)
begin
TempRawProfilingNodes.SetView(FilterText);
TempRawProfilingNodes.SetFilter("Self Time", '>%1', 0);
if TempRawProfilingNodes.FindSet() then
repeat
SetAggregationFilter(AggregatedProfilingNode, TempRawProfilingNodes, ProfilingAggregationType);
if AggregatedProfilingNode.FindFirst() then begin
AggregatedProfilingNode."Self Time" += TempRawProfilingNodes."Self Time";
AggregatedProfilingNode."Hit Count" += TempRawProfilingNodes."Hit Count";
AggregatedProfilingNode.Modify();
end else begin
AggregatedProfilingNode := TempRawProfilingNodes;
AggregatedProfilingNode.Insert();
end;
until TempRawProfilingNodes.Next() = 0;
TempRawProfilingNodes.Reset();
AggregatedProfilingNode.Reset();
end;
/// <summary>
/// Gets aggregate full time of profiling nodes using the provided aggregation type.
/// </summary>
/// <param name="AggregatedProfilingNode">The resulting aggregation.</param>
/// <param name="ProfilingAggregationType">The value of what to aggregate full time by.</param>
procedure GetFullTimeAggregate(var AggregatedProfilingNode: Record "Profiling Node"; ProfilingAggregationType: Enum "Profiling Aggregation Type")
begin
if ProfilingAggregationType = ProfilingAggregationType::None then begin
// just copy the contents of the call tree if no aggregation is needed
if TempCallTreeProfilingNodes.FindSet() then
repeat
AggregatedProfilingNode := TempCallTreeProfilingNodes;
AggregatedProfilingNode.Insert();
until TempCallTreeProfilingNodes.Next() = 0;
exit;
end;
// Run for all call stack top nodes
TempCallTreeProfilingNodes.SetRange(Indentation, 0);
TempCallTreeProfilingNodes.SetFilter("Full Time", '>%1', 0);
if TempCallTreeProfilingNodes.FindSet() then
repeat
ComputeFullTimeAggregate(TempCallTreeProfilingNodes, ProfilingAggregationType, AggregatedProfilingNode);
until TempCallTreeProfilingNodes.Next() = 0;
TempCallTreeProfilingNodes.Reset();
AggregatedProfilingNode.Reset();
end;
/// <summary>
/// Constructs and returns a unique string (with respect to aggregation type) from the provided profiling node.
/// </summary>
/// <param name="ProfilingNode">The node from which to construct a unique identifier.</param>
/// <param name="ProfilingAggregationType">The aggregation type.</param>
/// <returns>A unique (per aggregation type) string from the provided profiling node.</returns>
procedure GetUniqueIdentifierByAggregationType(ProfilingNode: Record "Profiling Node"; ProfilingAggregationType: Enum "Profiling Aggregation Type"): Text
begin
case ProfilingAggregationType of
ProfilingAggregationType::"App Publisher":
exit(ProfilingNode."App Publisher");
ProfilingAggregationType::"App Name":
exit(ProfilingNode."App Name");
ProfilingAggregationType::Object:
exit(ProfilingNode."Object Type" + Format(ProfilingNode."Object ID"));
ProfilingAggregationType::Method:
exit(ProfilingNode."Object Type" + Format(ProfilingNode."Object ID") + ProfilingNode."Method Name");
end;
end;
local procedure GetChildrenFilterView(var RootProfilingNode: Record "Profiling Node"): Text
var
TempNextNonChildProfilingNode: Record "Profiling Node";
TempChildrenProfilingNodes: Record "Profiling Node";
begin
TempChildrenProfilingNodes.SetRange(Indentation, RootProfilingNode.Indentation + 1);
TempNextNonChildProfilingNode.Copy(RootProfilingNode, true);
TempNextNonChildProfilingNode.SetFilter(Indentation, '<=%1', RootProfilingNode.Indentation);
TempNextNonChildProfilingNode.SetFilter("No.", '>%1', RootProfilingNode."No.");
if TempNextNonChildProfilingNode.FindFirst() then
TempChildrenProfilingNodes.SetFilter("No.", '>%1&<%2', RootProfilingNode."No.", TempNextNonChildProfilingNode."No.")
else
TempChildrenProfilingNodes.SetFilter("No.", '>%1', RootProfilingNode."No.");
exit(TempChildrenProfilingNodes.GetView());
end;
local procedure ComputeFullTimeAggregate(var RootProfilingNode: Record "Profiling Node"; ProfilingAggregationType: Enum "Profiling Aggregation Type"; var AggregatedProfilingNode: Record "Profiling Node")
var
TempChildProfilingNodes: Record "Profiling Node";
TempCurrProfilingNode: Record "Profiling Node";
ExclusionSet: DotNet GenericHashSet1;
ChildExclusionSet: DotNet GenericHashSet1;
NodeStack: DotNet Stack;
NodeNumberWithExclusionSet: DotNet GenericKeyValuePair2;
CurrProfilingNodeNumber: Integer;
begin
// Depth-first traversal
NodeStack := NodeStack.Stack();
TempCurrProfilingNode.Copy(RootProfilingNode, true);
// push the root node on the stack
ExclusionSet := ExclusionSet.HashSet();
NodeNumberWithExclusionSet := NodeNumberWithExclusionSet.KeyValuePair(RootProfilingNode."No.", ExclusionSet);
NodeStack.Push(NodeNumberWithExclusionSet);
while NodeStack.Count() > 0 do begin
// pop the current node from the stack and get all the information associated with it
NodeNumberWithExclusionSet := NodeStack.Pop();
CurrProfilingNodeNumber := NodeNumberWithExclusionSet."Key";
ExclusionSet := NodeNumberWithExclusionSet.Value;
TempCurrProfilingNode.Get(SessionId(), CurrProfilingNodeNumber);
// process the current node
AddNodeToAggregation(TempCurrProfilingNode, ProfilingAggregationType, ExclusionSet, AggregatedProfilingNode);
// push all the child nodes of the current node on the stack
TempChildProfilingNodes.Copy(TempCurrProfilingNode, true);
TempChildProfilingNodes.SetView(GetChildrenFilterView(TempCurrProfilingNode));
if TempChildProfilingNodes.FindSet() then
repeat
ChildExclusionSet := ChildExclusionSet.HashSet(ExclusionSet);
NodeNumberWithExclusionSet := NodeNumberWithExclusionSet.KeyValuePair(TempChildProfilingNodes."No.", ChildExclusionSet);
NodeStack.Push(NodeNumberWithExclusionSet);
until TempChildProfilingNodes.Next() = 0;
end;
end;
local procedure AddNodeToAggregation(CurrProfilingNode: Record "Profiling Node"; ProfilingAggregationType: Enum "Profiling Aggregation Type"; ExclusionSet: DotNet GenericHashSet1; var AggregatedProfilingNode: Record "Profiling Node")
var
AggregationTypeUniqueIdentifier: Text;
begin
// If the current AggregationTypeUniqueIdentifier has not been encountered yet (in the current branch of the tree), then add the full time of the current node to the aggregation.
AggregationTypeUniqueIdentifier := GetUniqueIdentifierByAggregationType(CurrProfilingNode, ProfilingAggregationType);
if not ExclusionSet.Contains(AggregationTypeUniqueIdentifier) then begin
ExclusionSet.Add(AggregationTypeUniqueIdentifier);
SetAggregationFilter(AggregatedProfilingNode, CurrProfilingNode, ProfilingAggregationType);
if AggregatedProfilingNode.FindFirst() then begin
AggregatedProfilingNode."Full Time" += CurrProfilingNode."Full Time";
AggregatedProfilingNode.Modify();
end else begin
AggregatedProfilingNode := CurrProfilingNode;
AggregatedProfilingNode.Insert();
end;
end;
end;
local procedure SetAggregationFilter(var AggregatedProfilingNode: Record "Profiling Node"; SourceProfilingNode: Record "Profiling Node"; ProfilingAggregationType: Enum "Profiling Aggregation Type")
begin
case ProfilingAggregationType of
ProfilingAggregationType::"App Publisher":
AggregatedProfilingNode.SetRange("App Publisher", SourceProfilingNode."App Publisher");
ProfilingAggregationType::"App Name":
begin
AggregatedProfilingNode.SetRange("App Publisher", SourceProfilingNode."App Publisher");
AggregatedProfilingNode.SetRange("App Name", SourceProfilingNode."App Name");
end;
ProfilingAggregationType::Object:
begin
AggregatedProfilingNode.SetRange("Object Type", SourceProfilingNode."Object Type");
AggregatedProfilingNode.SetRange("Object ID", SourceProfilingNode."Object ID");
end;
ProfilingAggregationType::Method:
begin
AggregatedProfilingNode.SetRange("Object Type", SourceProfilingNode."Object Type");
AggregatedProfilingNode.SetRange("Object ID", SourceProfilingNode."Object ID");
AggregatedProfilingNode.SetRange("Method Name", SourceProfilingNode."Method Name");
end;
end;
end;
[EventSubscriber(ObjectType::Codeunit, Codeunit::"System Action Triggers", GetPerformanceTroubleshooterPageId, '', false, false)]
local procedure GetPerformanceTroubleshooterPageId(var PageId: Integer)
begin
PageId := Page::"Performance Profiler";
end;
}