Skip to content

Commit

Permalink
Adds a rendering performance test
Browse files Browse the repository at this point in the history
  • Loading branch information
albyrock87 committed Sep 19, 2024
1 parent 049ec59 commit 6df2c93
Show file tree
Hide file tree
Showing 4 changed files with 269 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:issues="clr-namespace:Maui.Controls.Sample.Issues"
x:Class="Maui.Controls.Sample.Issues.RenderingPerformance"
Title="RenderingPerformance">

<Grid Padding="24" RowDefinitions="Auto,*" RowSpacing="8">
<Button x:Name="StartButton" Clicked="ButtonClicked" Text="Start" AutomationId="StartButton" />

<ScrollView Grid.Row="1">
<ContentView>
<VerticalStackLayout x:Name="BindableContainer" BindableLayout.ItemsSource="{Binding Models}">
<BindableLayout.ItemTemplate>
<DataTemplate>
<ContentView>
<Grid RowDefinitions="Auto,Auto,Auto">
<Label Text="{Binding Header}" />
<Label Text="{Binding Content}" Grid.Row="1" />
<VerticalStackLayout Grid.Row="2" BindableLayout.ItemsSource="{Binding SubModels}">
<BindableLayout.ItemTemplate>
<DataTemplate>
<ContentView>
<VerticalStackLayout>
<Label Text="{Binding Header}" />
<issues:MeasuredLabel IsMeasured="{Binding IsMeasured}" Text="{Binding Content}" />
</VerticalStackLayout>
</ContentView>
</DataTemplate>
</BindableLayout.ItemTemplate>
</VerticalStackLayout>
</Grid>
</ContentView>
</DataTemplate>
</BindableLayout.ItemTemplate>
</VerticalStackLayout>
</ContentView>
</ScrollView>
</Grid>
</ContentPage>
Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
using System;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Globalization;
using Microsoft.Maui;
using Microsoft.Maui.Controls;
using Microsoft.Maui.Controls.Xaml;
using ILayout = Microsoft.Maui.ILayout;

namespace Maui.Controls.Sample.Issues;

public class MeasuredLabel : Label
{
private static readonly TimeSpan ArrangedThreshold = TimeSpan.FromSeconds(1);
public static readonly BindableProperty IsMeasuredProperty = BindableProperty.Create(nameof(IsMeasured), typeof(bool), typeof(MeasuredLabel), false);

public bool IsMeasured
{
get => (bool)GetValue(IsMeasuredProperty);
set => SetValue(IsMeasuredProperty, value);
}

public long? LastArrangedTicks { get; set; }

public long? GetArrangeTicks() {
if (LastArrangedTicks is { } ticks)
{
var elapsed = Stopwatch.GetElapsedTime(ticks);
if (elapsed > ArrangedThreshold)
{
return ticks;
}
}

return null;
}
}

public static class RenderingPerformanceExtensions
{
public static MauiAppBuilder RenderingPerformanceAddMappers(this MauiAppBuilder builder)
{
builder.ConfigureMauiHandlers(handlers =>
{
Microsoft.Maui.Handlers.LabelHandler.CommandMapper.AppendToMapping(nameof(IView.Frame), (handler, view, arg) =>
{
if (view is MeasuredLabel { IsMeasured: true } measuredLabel)
{
measuredLabel.LastArrangedTicks = Stopwatch.GetTimestamp();
}
});
});

return builder;
}
}

[XamlCompilation(XamlCompilationOptions.Compile)]
[Issue(IssueTracker.None, 0, "Rendering performance", PlatformAffected.All)]
public partial class RenderingPerformance : ContentPage
{
bool _firstRun = true;

public List<Model> Models { get; set; }

public RenderingPerformance()
{
Models = GenerateMeasuredItem();
BindingContext = this;
InitializeComponent();
}

private async void ButtonClicked(object sender, EventArgs e)
{
var capturedTimes = new List<int[]>();

var test1Models = GenerateItems(50, "Test1");
var test2Models = GenerateItems(25, "Test2");
var resetModel = GenerateMeasuredItem();

if (_firstRun)
{
_firstRun = false;
await GetArrangeTicksAsync();
}

for (var i = 0; i < 6; i++)
{
await Task.Delay(200);

Models = test1Models;
var startTicks = Stopwatch.GetTimestamp();
OnPropertyChanged(nameof(Models));
var endTicks = await Task.Run(GetArrangeTicksAsync);
var t1 = (int)Stopwatch.GetElapsedTime(startTicks, endTicks).TotalMilliseconds;

await Task.Delay(200);

Models = test2Models;
startTicks = Stopwatch.GetTimestamp();
OnPropertyChanged(nameof(Models));
endTicks = await Task.Run(GetArrangeTicksAsync);
var t2 = (int)Stopwatch.GetElapsedTime(startTicks, endTicks).TotalMilliseconds;

await Task.Delay(200);

startTicks = Stopwatch.GetTimestamp();
BindableContainer.Clear();
Models = resetModel;
OnPropertyChanged(nameof(Models));
endTicks = await Task.Run(GetArrangeTicksAsync);
var t3 = (int)Stopwatch.GetElapsedTime(startTicks, endTicks).TotalMilliseconds;

capturedTimes.Add([t1, t2, t3]);
}

var avg1 = (int)capturedTimes.Average(t => t[0]);
var avg2 = (int)capturedTimes.Average(t => t[1]);
var avg3 = (int)capturedTimes.Average(t => t[2]);
StartButton.Text = $"{avg1},{avg2},{avg3}";
}

async Task<long> GetArrangeTicksAsync()
{
while (true)
{
await Task.Delay(100);
IView view = BindableContainer;
while (true)
{
if (view is ILayout { Count: > 0 } layout)
{
view = layout[^1];
}
else if (view is IContentView contentView)
{
view = (IView)contentView.Content;
}
else
{
break;
}
}

if (view is MeasuredLabel measuredLabel && measuredLabel.GetArrangeTicks() is { } arrangeTicks)
{
measuredLabel.LastArrangedTicks = null;
return arrangeTicks;
}
}
}

static List<Model> GenerateItems(int count, string prefix)
{
return
[
..Enumerable.Range(0, count).Select(i => new Model
{
Content = $"{prefix} Content {i}",
Header = $"Header {i}",
SubModels = Enumerable.Range(0, 10).Select(j => new SubModel
{
Content = $"{prefix} SubContent {j}", Header = $"{prefix} SubHeader {j}"
}).ToArray()
}),
..GenerateMeasuredItem()
];
}

static List<Model> GenerateMeasuredItem()
{
return
[
new Model
{
Content = "Measured",
Header = "Measured",
SubModels =
[
new SubModel { Content = "Measured", Header = "Measured", IsMeasured = true }
]
}
];
}

public class Model : SubModel
{
public SubModel[] SubModels { get; set; }
}

public class SubModel
{
public string Header { get; set; }
public string Content { get; set; }
public bool IsMeasured { get; set; }
}
}
1 change: 1 addition & 0 deletions src/Controls/tests/TestCases.HostApp/MauiProgram.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public static MauiApp CreateMauiApp()
fonts.AddFont("FontAwesome.ttf", "FA");
fonts.AddFont("ionicons.ttf", "Ion");
})
.RenderingPerformanceAddMappers()
.Issue21109AddMappers()
.Issue18720AddMappers()
.Issue18720EditorAddMappers()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using NUnit.Framework;
using UITest.Appium;
using UITest.Core;

namespace Microsoft.Maui.TestCases.Tests.Issues
{
public class RenderingPerformance : _IssuesUITest
{
public RenderingPerformance(TestDevice device) : base(device) { }

public override string Issue => "Rendering performance";

[Test]
[Category(UITestCategories.Performance)]
public async Task RenderingPerformanceRun()
{
const string automationId = "StartButton";

var button = App.WaitForElement(automationId);
App.Tap(automationId);

App.WaitForTextToBePresentInElement(automationId, ",");

var times = button.GetText()?.Split(',') ?? throw new ArgumentNullException("StartButton text is null");

var logFile = Path.Combine(Path.GetTempPath(), "RenderingPerformance.log");
await File.WriteAllTextAsync(logFile, @$"RenderingPerformance: [{times[0]}, {times[1]}, {times[2]}]");
TestContext.AddTestAttachment(logFile, "RenderingPerformance.log");
}
}
}

0 comments on commit 6df2c93

Please sign in to comment.