Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 36 additions & 7 deletions src/Controls/samples/Controls.Sample/MauiProgram.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.Metrics;
using System.Runtime.CompilerServices;
using Maui.Controls.Sample.Controls;
using Maui.Controls.Sample.Pages;
using Maui.Controls.Sample.Services;
Expand Down Expand Up @@ -69,20 +71,47 @@ public static MauiApp CreateMauiApp()
#endif
}


appBuilder.Services.AddMetrics();
ActivitySource.AddActivityListener(new ActivityListener
{
ShouldListenTo = source => true,
Sample = (ref ActivityCreationOptions<ActivityContext> options) => ActivitySamplingResult.AllDataAndRecorded,
ActivityStarted = activity => Console.WriteLine("Started: {0,-15} {1,-60}", activity.OperationName, activity.Id),
ActivityStopped = activity => Console.WriteLine("Stopped: {0,-15} {1,-60} {2,-15}", activity.OperationName, activity.Id, activity.Duration)
});

MeterListener meterListener = new();
meterListener.InstrumentPublished = (instrument, listener) =>
{
if (instrument.Meter.Name is "Microsoft.Maui.Diagnostics")
{
listener.EnableMeasurementEvents(instrument);
}
};

meterListener.SetMeasurementEventCallback<int>((Instrument instrument, int measurement, ReadOnlySpan<KeyValuePair<string, object?>> tags, object? state) =>
{
Console.WriteLine($"{instrument.Name} recorded measurement {measurement}");
});
// Start the meterListener, enabling InstrumentPublished callbacks.
meterListener.Start();


if (UseMauiGraphicsSkia)
{
/*
appBuilder.ConfigureMauiHandlers(handlers =>
{
handlers.AddHandler<GraphicsView, SkiaGraphicsViewHandler>();
handlers.AddHandler<BoxView, SkiaShapeViewHandler>();
handlers.AddHandler<Microsoft.Maui.Controls.Shapes.Ellipse, SkiaShapeViewHandler>();
handlers.AddHandler<Microsoft.Maui.Controls.Shapes.Line, SkiaShapeViewHandler>();
handlers.AddHandler<Microsoft.Maui.Controls.Shapes.Path, SkiaShapeViewHandler>();
handlers.AddHandler<Microsoft.Maui.Controls.Shapes.Polygon, SkiaShapeViewHandler>();
handlers.AddHandler<Microsoft.Maui.Controls.Shapes.Polyline, SkiaShapeViewHandler>();
handlers.AddHandler<Microsoft.Maui.Controls.Shapes.Rectangle, SkiaShapeViewHandler>();
handlers.AddHandler<Microsoft.Maui.Controls.Shapes.RoundRectangle, SkiaShapeViewHandler>();
handlers.AddHandler<Microsoft.Maui.Controls.Shapes.Ellipse, SkiaShapeViewHandler>();
handlers.AddHandler<Microsoft.Maui.Controls.Shapes.Line, SkiaShapeViewHandler>();
handlers.AddHandler<Microsoft.Maui.Controls.Shapes.Path, SkiaShapeViewHandler>();
handlers.AddHandler<Microsoft.Maui.Controls.Shapes.Polygon, SkiaShapeViewHandler>();
handlers.AddHandler<Microsoft.Maui.Controls.Shapes.Polyline, SkiaShapeViewHandler>();
handlers.AddHandler<Microsoft.Maui.Controls.Shapes.Rectangle, SkiaShapeViewHandler>();
handlers.AddHandler<Microsoft.Maui.Controls.Shapes.RoundRectangle, SkiaShapeViewHandler>();
});
*/
}
Expand Down
70 changes: 70 additions & 0 deletions src/Controls/src/Core/Diagnostics/MetricsTracker.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
using System;
using System.Diagnostics;
using Microsoft.Maui.Diagnostics;

namespace Microsoft.Maui.Controls.Diagnostics;

enum DiagnosticsMeasuring
{
None,
Measure,
Arrange
}

readonly struct MetricsTracker : IDisposable
{
readonly Activity? _activity;
readonly IView _view;
readonly DiagnosticsMeasuring _activityName;

public static MetricsTracker? Create(IView view, DiagnosticsMeasuring diagnosticsMeasuring)
{
if (RuntimeFeature.IsMeterSupported)
return new MetricsTracker(view, diagnosticsMeasuring);

return null;
}

MetricsTracker(IView view, DiagnosticsMeasuring diagnosticsMeasuring)
{
_view = view;
_activity = view.StartActivity($"{diagnosticsMeasuring}");
_activityName = diagnosticsMeasuring;

if (_activity is null)
return;

if (view is Element element)
{
_activity.SetTag("element.id", element.Id);
_activity.SetTag("element.automation_id", element.AutomationId);
_activity.SetTag("element.class_id", element.ClassId);
_activity.SetTag("element.style_id", element.StyleId);
}

if (view is VisualElement ve)
{
_activity.SetTag("element.class", ve.@class);
_activity.SetTag("element.frame", ve.Frame);
}
}

public void Dispose()
{
_activity?.Stop();

switch (_activityName)
{
case DiagnosticsMeasuring.Arrange:
_view.RecordArrange(_activity?.Duration);
break;
case DiagnosticsMeasuring.Measure:
_view.RecordMeasure(_activity?.Duration);
break;
default:
break;
}

_activity?.Dispose();
}
}
7 changes: 5 additions & 2 deletions src/Controls/src/Core/VisualElement/VisualElement.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using Microsoft.Maui.Controls.Diagnostics;
using Microsoft.Maui.Controls.Internals;
using Microsoft.Maui.Controls.Shapes;

using Microsoft.Maui.Diagnostics;
using Microsoft.Maui.Graphics;
using Microsoft.Maui.Layouts;
using Geometry = Microsoft.Maui.Controls.Shapes.Geometry;
Expand Down Expand Up @@ -1888,11 +1889,12 @@ public int ZIndex
public void Arrange(Rect bounds)
{
ArrangeOverride(bounds);
}
}

/// <inheritdoc/>
Size IView.Arrange(Rect bounds)
{
using var activity = MetricsTracker.Create(this, DiagnosticsMeasuring.Arrange);
return ArrangeOverride(bounds);
}

Expand Down Expand Up @@ -1947,6 +1949,7 @@ void IView.InvalidateArrange()
/// <inheritdoc/>
Size IView.Measure(double widthConstraint, double heightConstraint)
{
using var activity = MetricsTracker.Create(this, DiagnosticsMeasuring.Measure);
DesiredSize = MeasureOverride(widthConstraint, heightConstraint);
return DesiredSize;
}
Expand Down
89 changes: 89 additions & 0 deletions src/Core/src/Diagnostics/MauiDiagnostics.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
using System;
using System.Diagnostics;
using System.Diagnostics.Metrics;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Maui.Hosting;

namespace Microsoft.Maui.Diagnostics;

internal class MauiDiagnostics
{
public MauiDiagnostics(IMeterFactory? meterFactory = null)
{
ActivitySource = new ActivitySource("Microsoft.Maui.Diagnostics", "1.0.0");

Meters = meterFactory?.Create("Microsoft.Maui.Diagnostics", "1.0.0");

MeasureCounter = Meters?.CreateCounter<int>("maui.layout.measure_count", "{times}", "The number of times a measure happened.");
ArrangeCounter = Meters?.CreateCounter<int>("maui.layout.arrange_count", "{times}", "The number of times an arrange happened.");

MeasureHistogram = Meters?.CreateHistogram<int>("maui.layout.measure_duration", "ns");
ArrangeHistogram = Meters?.CreateHistogram<int>("maui.layout.arrange_duration", "ns");
}

public ActivitySource ActivitySource { get; }
public Meter? Meters { get; }
public Counter<int>? MeasureCounter { get; }
public Counter<int>? ArrangeCounter { get; }
public Histogram<int>? MeasureHistogram { get; }
public Histogram<int>? ArrangeHistogram { get; }
}

internal static class MauiDiagnosticsExtensions
{
public static MauiAppBuilder ConfigureMauiDiagnostics(this MauiAppBuilder builder)
{
if (RuntimeFeature.IsMeterSupported)
{
builder.Services.AddSingleton(serviceProvider => new MauiDiagnostics(serviceProvider.GetService<IMeterFactory>()));
}

return builder;
}

static MauiDiagnostics? GetMauiDiagnostics(this IView view)
{
if (!RuntimeFeature.IsMeterSupported)
return null;

return view.Handler?.MauiContext?.Services.GetService<MauiDiagnostics>();
}

public static Activity? StartActivity(this IView view, string name)
{
if (!RuntimeFeature.IsMeterSupported)
return null;

var elementName = view.GetType().Name;

var activity = view.GetMauiDiagnostics()?.ActivitySource.StartActivity($"{name} {elementName}");

activity?.SetTag("element.type", view.GetType().FullName);

return activity;
}

public static void RecordMeasure(this IView view, TimeSpan? duration)
{
if (!RuntimeFeature.IsMeterSupported)
return;

var diag = view.GetMauiDiagnostics();
diag?.MeasureCounter?.Add(1);

if (duration is not null)
diag?.MeasureHistogram?.Record((int)duration.Value.TotalNanoseconds);
}

public static void RecordArrange(this IView view, TimeSpan? duration)
{
if (!RuntimeFeature.IsMeterSupported)
return;

var diag = view.GetMauiDiagnostics();
diag?.ArrangeCounter?.Add(1);

if (duration is not null)
diag?.ArrangeHistogram?.Record((int)duration.Value.TotalNanoseconds);
}
}
2 changes: 2 additions & 0 deletions src/Core/src/Hosting/MauiAppBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Maui.Diagnostics;
using Microsoft.Maui.LifecycleEvents;

namespace Microsoft.Maui.Hosting
Expand Down Expand Up @@ -53,6 +54,7 @@ internal MauiAppBuilder(bool useDefaults)
this.ConfigureWindowEvents();
this.ConfigureDispatching();
this.ConfigureEnvironmentVariables();
this.ConfigureMauiDiagnostics();

this.UseEssentials();

Expand Down
11 changes: 11 additions & 0 deletions src/Core/src/RuntimeFeature.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ static class RuntimeFeature
const bool IsHybridWebViewSupportedByDefault = true;
const bool SupportNamescopesByDefault = true;
const bool EnableDiagnosticsByDefault = false;
const bool IsMeterSupportedByDefault = true;

#pragma warning disable IL4000 // Return value does not match FeatureGuardAttribute 'System.Diagnostics.CodeAnalysis.RequiresUnreferencedCodeAttribute'.
#if NET9_0_OR_GREATER
Expand Down Expand Up @@ -102,6 +103,16 @@ public static bool AreNamescopesSupported

}

// https://github.com/dotnet/runtime/blob/8c7de742a77ed3919a3f3fe8c4475fce689f5e83/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/EventSource.cs#L291-L295
#if NET9_0_OR_GREATER
[FeatureSwitchDefinition($"System.Diagnostics.Metrics.Meter.IsSupported")]
#endif
public static bool IsMeterSupported => AppContext.TryGetSwitch($"System.Diagnostics.Metrics.Meter.IsSupported", out bool isEnabled)
? isEnabled
: IsMeterSupportedByDefault;



#if NET9_0_OR_GREATER
[FeatureSwitchDefinition($"{FeatureSwitchPrefix}.{nameof(EnableDiagnostics)}")]
#endif
Expand Down
Loading