Codeunit 1923 Profiling Data Processor, source in 24
Source242526272829metadata in 24
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
RawProfilingNodes: Record "Profiling Node";
CallTreeProfilingNodes: 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
RawProfilingNodes.Copy(RawProfilingNode, true);
CallTreeProfilingNodes.Copy(CallTreeProfilingNode, true);
end;
/// <summary>
/// Clears the previously initialized performance profiling data.
/// </summary>
procedure ClearData();
begin
RawProfilingNodes.DeleteAll();
CallTreeProfilingNodes.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 RawProfilingNodes.IsEmpty() then
exit(false);
if CallTreeProfilingNodes.IsEmpty() then
exit(false);
RawProfilingNodes.CalcSums("Self Time");
if RawProfilingNodes."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
RawProfilingNodes.SetView(FilterText);
RawProfilingNodes.SetFilter("Self Time", '>%1', 0);
if RawProfilingNodes.FindSet() then
repeat
SetAggregationFilter(AggregatedProfilingNode, RawProfilingNodes, ProfilingAggregationType);
if AggregatedProfilingNode.FindFirst() then begin
AggregatedProfilingNode."Self Time" += RawProfilingNodes."Self Time";
AggregatedProfilingNode."Hit Count" += RawProfilingNodes."Hit Count";
AggregatedProfilingNode.Modify();
end else begin
AggregatedProfilingNode := RawProfilingNodes;
AggregatedProfilingNode.Insert();
end;
until RawProfilingNodes.Next() = 0;
RawProfilingNodes.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 CallTreeProfilingNodes.FindSet() then
repeat
AggregatedProfilingNode := CallTreeProfilingNodes;
AggregatedProfilingNode.Insert();
until CallTreeProfilingNodes.Next() = 0;
exit;
end;
// Run for all call stack top nodes
CallTreeProfilingNodes.SetRange(Indentation, 0);
CallTreeProfilingNodes.SetFilter("Full Time", '>%1', 0);
if CallTreeProfilingNodes.FindSet() then
repeat
ComputeFullTimeAggregate(CallTreeProfilingNodes, ProfilingAggregationType, AggregatedProfilingNode);
until CallTreeProfilingNodes.Next() = 0;
CallTreeProfilingNodes.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
NextNonChildProfilingNode: Record "Profiling Node";
ChildrenProfilingNodes: Record "Profiling Node";
begin
ChildrenProfilingNodes.SetRange(Indentation, RootProfilingNode.Indentation + 1);
NextNonChildProfilingNode.Copy(RootProfilingNode, true);
NextNonChildProfilingNode.SetFilter(Indentation, '<=%1', RootProfilingNode.Indentation);
NextNonChildProfilingNode.SetFilter("No.", '>%1', RootProfilingNode."No.");
if NextNonChildProfilingNode.FindFirst() then
ChildrenProfilingNodes.SetFilter("No.", '>%1&<%2', RootProfilingNode."No.", NextNonChildProfilingNode."No.")
else
ChildrenProfilingNodes.SetFilter("No.", '>%1', RootProfilingNode."No.");
exit(ChildrenProfilingNodes.GetView());
end;
local procedure ComputeFullTimeAggregate(var RootProfilingNode: Record "Profiling Node"; ProfilingAggregationType: Enum "Profiling Aggregation Type"; var AggregatedProfilingNode: Record "Profiling Node")
var
ChildProfilingNodes: Record "Profiling Node";
CurrProfilingNode: Record "Profiling Node";
ExclusionSet: DotNet GenericHashSet1;
ChildExclusionSet: DotNet GenericHashSet1;
NodeStack: DotNet Stack;
NodeNumberWithExclusionSet: DotNet GenericKeyValuePair2;
CurrProfilingNodeNumber: Integer;
begin
// Depth-first traversal
NodeStack := NodeStack.Stack();
CurrProfilingNode.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;
CurrProfilingNode.Get(SessionId(), CurrProfilingNodeNumber);
// process the current node
AddNodeToAggregation(CurrProfilingNode, ProfilingAggregationType, ExclusionSet, AggregatedProfilingNode);
// push all the child nodes of the current node on the stack
ChildProfilingNodes.Copy(CurrProfilingNode, true);
ChildProfilingNodes.SetView(GetChildrenFilterView(CurrProfilingNode));
if ChildProfilingNodes.FindSet() then
repeat
ChildExclusionSet := ChildExclusionSet.HashSet(ExclusionSet);
NodeNumberWithExclusionSet := NodeNumberWithExclusionSet.KeyValuePair(ChildProfilingNodes."No.", ChildExclusionSet);
NodeStack.Push(NodeNumberWithExclusionSet);
until ChildProfilingNodes.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;
}