Codeunit 1352 Telemetry Log Metrics, source in 29
Source29
src/Layers/W1/BaseApp/System/Telemetry/TelemetryLogMetrics.Codeunit.al134 lines, Copyright (c) Microsoft Corporation. MIT
namespace System.Telemetry;
/// <summary>
/// This codeunit collects metrics that will be emitted as telemetry when the session ends.
/// </summary>
codeunit 1352 "Telemetry Log Metrics"
{
SingleInstance = true;
InherentEntitlements = X;
InherentPermissions = X;
Access = internal;
var
Metrics: List of [Text];
NoOfMeasurements: Dictionary of [Text, Decimal];
SumOfMeasurements: Dictionary of [Text, Decimal];
MinMeasurements: Dictionary of [Text, Decimal];
MaxMeasurements: Dictionary of [Text, Decimal];
StartTimes: Dictionary of [Text, DateTime];
/// <summary>
/// Registers the start time of a process, labeled by MeasureName
/// </summary>
/// <param name="MeasureName">A name that indicates the scope of what is being measured, e.g. a function name or some process. NB! Must not contain user data!</param>
[TryFunction]
procedure StartMeasureTime(MeasureName: Text)
begin
if EnsureEntryExists(MeasureName, StartTimes) then
StartTimes.Set(MeasureName, CurrentDateTime());
end;
/// <summary>
/// Stores the elapsed time for MeasureName
/// </summary>
/// <param name="MeasureName">A name that indicates the scope of what is being measured, e.g. a function name or some process. NB! Must not contain user data!</param>
[TryFunction]
procedure StopMeasureTime(MeasureName: Text)
begin
if EnsureEntryExists(MeasureName, StartTimes) then
if LogMeasure(MeasureName, CurrentDateTime() - StartTimes.Get(MeasureName)) then
StartTimes.Set(MeasureName, CurrentDateTime());
end;
/// <summary>
/// Stores the AddedValue for MeasureName
/// </summary>
/// <param name="MeasureName">A name that indicates the scope of what is being measured, e.g. a function name or some process. NB! Must not contain user data!</param>
[TryFunction]
procedure LogMeasure(MeasureName: Text; AddedValue: Decimal)
var
CurrentValue: Decimal;
begin
EnsureEntryExists(MeasureName, NoOfMeasurements, 0);
EnsureEntryExists(MeasureName, SumOfMeasurements, 0);
EnsureEntryExists(MeasureName, MinMeasurements, 999999999999999.9);
EnsureEntryExists(MeasureName, MaxMeasurements, -999999999999999.9);
CurrentValue := NoOfMeasurements.Get(MeasureName);
NoOfMeasurements.Set(MeasureName, CurrentValue + 1);
CurrentValue := SumOfMeasurements.Get(MeasureName);
SumOfMeasurements.Set(MeasureName, CurrentValue + AddedValue);
CurrentValue := MinMeasurements.Get(MeasureName);
if AddedValue < CurrentValue then
MinMeasurements.Set(MeasureName, AddedValue);
CurrentValue := MaxMeasurements.Get(MeasureName);
if AddedValue > CurrentValue then
MaxMeasurements.Set(MeasureName, AddedValue);
end;
/// <summary>
/// Sends collected metrics to telemetry and clears variables
/// </summary>
[TryFunction]
internal procedure FlushMetricsToTelemetry()
var
CustomDimensions: Dictionary of [Text, Text];
MetricName: Text;
AverageTxt: Text;
SkipTelemetry: Boolean;
begin
OnBeforeFlushMetricsToTelemetry(SkipTelemetry, SumOfMeasurements, NoOfMeasurements, MinMeasurements, MaxMeasurements);
if SkipTelemetry then
exit;
foreach MetricName in Metrics do
if NoOfMeasurements.Get(MetricName) > 0 then begin
Clear(CustomDimensions);
AverageTxt := Format(Round(SumOfMeasurements.Get(MetricName) / NoOfMeasurements.Get(MetricName), 0.001), 0, 9);
CustomDimensions.Add('MetricName', MetricName);
CustomDimensions.Add('NoOfMeasures', Format(NoOfMeasurements.Get(MetricName), 0, 9));
CustomDimensions.Add('SumOfMeasurements', Format(SumOfMeasurements.Get(MetricName), 0, 9));
CustomDimensions.Add('Average', AverageTxt);
CustomDimensions.Add('MinValue', Format(MinMeasurements.Get(MetricName), 0, 9));
CustomDimensions.Add('MaxValue', Format(MaxMeasurements.Get(MetricName), 0, 9));
Session.LogMessage('0000REN', 'Metric: ' + MetricName + ' Average=' + AverageTxt, Verbosity::Normal, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, CustomDimensions);
end;
ClearAll();
end;
[TryFunction]
local procedure EnsureEntryExists(MeasureName: Text; var Measurements: Dictionary of [Text, Decimal]; InitValue: Decimal)
begin
if not Metrics.Contains(MeasureName) then
Metrics.Add(MeasureName);
if not Measurements.ContainsKey(MeasureName) then
Measurements.Add(MeasureName, InitValue);
end;
[TryFunction]
local procedure EnsureEntryExists(MeasureName: Text; var Measurements: Dictionary of [Text, DateTime])
begin
if Metrics.Contains(MeasureName) then
exit;
Metrics.Add(MeasureName);
if not Measurements.ContainsKey(MeasureName) then
Measurements.Add(MeasureName, CurrentDateTime());
end;
/// <summary>
/// This event is mainly intended for test to verify that we collect the expected metrics.
/// </summary>
/// <param name="SkipTelemetry">Set this variable to true to avoid sending telemetry</param>
/// <param name="SumOfMeasurements">Dictionary of [Text, Decimal]</param>
/// <param name="NoOfMeasurements">Dictionary of [Text, Decimal]</param>
/// <param name="MinMeasurements">Dictionary of [Text, Decimal]</param>
/// <param name="MaxMeasurements">Dictionary of [Text, Decimal]</param>
[IntegrationEvent(false, false)]
local procedure OnBeforeFlushMetricsToTelemetry(var SkipTelemetry: Boolean; SumOfMeasurements: Dictionary of [Text, Decimal]; NoOfMeasurements: Dictionary of [Text, Decimal]; MinMeasurements: Dictionary of [Text, Decimal]; MaxMeasurements: Dictionary of [Text, Decimal])
begin
end;
}