Maude is a .NET-native, in-app performance tracker for .NET for iOS, Android, Mac Catalyst, and .NET MAUI. This guide covers integration details, runtime APIs, and advanced configuration beyond the README quickstart.
Provide the activity or window Maude should use for presenting its overlay:
// In your Activity
var options = MaudeOptions.CreateBuilder()
.WithPresentationWindowProvider(() => this) // required on Android
.Build();
MaudeRuntime.InitializeAndActivate(options);The delegate should return the current foreground activity; Maude will throw if PresentationWindowProvider is missing on Android.
The default window provider uses the key UIWindow, so you can initialise without extra configuration:
MaudeRuntime.InitializeAndActivate();Provide a custom window via WithPresentationWindowProvider if you need to target a non-key scene or window.
MAUI on Android must supply a delegate that returns the current activity; WithMauiWindowProvider wires up Platform.CurrentActivity for you.
// MauiProgram.cs
using Maude;
var maudeOptions = MaudeOptions.CreateBuilder()
.WithMauiWindowProvider() // required on Android
.Build();
var builder = MauiApp.CreateBuilder()
.UseMauiApp<App>()
.UseMaude(maudeOptions);
MaudeRuntime.Activate(); // or builder.UseMaudeAndActivate(maudeOptions)If you provide your own delegate, pass it via WithPresentationWindowProvider(() => Platform.CurrentActivity). Without one, MAUI/Android will throw during startup.
Once initialised and activated, present or dismiss Maude from anywhere in your app:
MaudeRuntime.PresentSheet();
MaudeRuntime.DismissSheet();
MaudeRuntime.PresentOverlay();
MaudeRuntime.DismissOverlay();Record markers and additional metrics so memory spikes have context.
// Define channels first (avoid reserved IDs 0, 1 and 255).
var channels = new []
{
new MaudeChannel(96, "Image Cache", Colors.Orange),
new MaudeChannel(97, "Network Buffers", Colors.Green)
};
// Initialise Maude with those channels.
var options = MaudeOptions.CreateBuilder()
.WithAdditionalChannels(channels)
.Build();
MaudeRuntime.InitializeAndActivate(options);
// Add metrics (rendered as extra series).
MaudeRuntime.Metric(currentCacheSizeBytes, 96);
// Add events (rendered as vertical markers + items in the event list).
MaudeRuntime.Event("Cache cleared", 96); // default type + icon "*"
MaudeRuntime.Event("GC requested", MaudeEventType.Gc); // GC event symbol "g"
MaudeRuntime.Event("Large download", MaudeEventType.Event, 97, "42 MB");Events/metrics on unknown channels are ignored. Both the slide-in sheet and overlay display the channels and event markers, letting you correlate spikes with the moments you annotated.
Use the MaudeOptionsBuilder to tune sampling, channels, gestures and logging:
var options = MaudeOptions.CreateBuilder()
.WithSampleFrequencyMilliseconds(500) // clamp: 200–2000 ms
.WithRetentionPeriodSeconds(10 * 60) // clamp: 60–3600 s
.WithAdditionalChannels(customChannels) // extra metric/event series
.WithDefaultMemoryChannels(MaudeDefaultMemoryChannels.PlatformDefaults) // iOS/Android sensible defaults
.WithShakeGesture() // enable shake-to-toggle
.WithDefaultOverlayPosition(MaudeOverlayPosition.TopRight) // default anchor when showing overlay without an explicit position
.WithShakeGestureBehaviour(MaudeShakeGestureBehaviour.Overlay) // or SlideSheet
.WithEventRenderingBehaviour(MaudeEventRenderingBehaviour.IconsOnly) // LabelsAndIcons, IconsOnly (default), None
.WithChartTheme(MaudeChartTheme.Light) // Light or Dark (default)
.WithAdditionalLogger(new MyLogger()) // or .WithBuiltInLogger()
.WithPresentationWindowProvider(() => /* your Android Activity or window */)
.WithSaveSnapshotAction((snapshot) => Persist(snapshot), "SAVE")
.Build();Use WithEventRenderingBehaviour (or change MaudeRuntime.EventRenderingBehaviour at runtime) to choose between icons with labels, icons only, or hiding events entirely. This applies to both the slide sheet and overlay chart.
Switch themes on the fly by updating MaudeRuntime.ChartTheme:
MaudeRuntime.ChartTheme = MaudeChartTheme.Light; // or DarkMaudeOptions now exposes WithDefaultMemoryChannels and WithoutDefaultMemoryChannels so you can decide which of Maude's built-in metrics appear. The flags in MaudeDefaultMemoryChannels cover the CLR managed heap, Android's native heap, Android's RSS, and iOS's physical footprint channel.
var options = MaudeOptions.CreateBuilder()
.WithDefaultMemoryChannels(MaudeDefaultMemoryChannels.ManagedHeap) // managed heap only
.Build();
var hideNoise = MaudeOptions.CreateBuilder()
.WithoutDefaultMemoryChannels(MaudeDefaultMemoryChannels.NativeHeap | MaudeDefaultMemoryChannels.ResidentSetSize)
.Build();By default Maude enables the platform-appropriate channels (CLR + Native + RSS on Android, CLR + Physical Footprint on iOS). Supplying MaudeDefaultMemoryChannels.None hides every built-in memory series so you can overlay only your custom channels.
When you need to gate the shake gesture behind your own runtime configuration, provide a predicate. Maude consults it before activating the listener and each time a shake occurs, so you don't have to manually call EnableShakeGesture or DisableShakeGesture as your config changes.
var options = MaudeOptions.CreateBuilder()
.WithShakeGesture()
.WithShakeGesturePredicate(() => MyDebugConfig.IsShakeAllowed)
.Build();If the predicate returns false, the accelerometer remains registered but shakes are ignored until the predicate later returns true. If it throws, Maude logs the exception and suppresses the shake.
While the MAUI app builder extension registers Maude for you, you may want sampling to start before your UI loads:
- Android: initialise inside
MainApplicationso the runtime is ready beforeCreateMauiApp()or your first activity starts. - iOS/macOS Catalyst: initialise before
UIApplication.MaininProgram.cs:
var options = /* build options */;
MaudeRuntime.InitializeAndActivate(options);
UIApplication.Main(args, null, typeof(AppDelegate));Maude can sample frames-per-second alongside memory metrics and overlay the results on the chart. Enable FPS capture when building options:
var options = MaudeOptions.CreateBuilder()
.WithFramesPerSecond()
.Build();At runtime you can call MaudeRuntime.EnableFramesPerSecond() or .DisableFramesPerSecond() to toggle sampling without rebuilding the options. FPS series segments automatically change color as the rate crosses the built-in thresholds (Optimal ≥50, Stable 40–49, Fair 30–39, Poor 20–29, Critical <20) so jank is easy to spot.