Skip to content

Diagnostics

Nicolás Seijas edited this page Jul 28, 2026 · 1 revision

Diagnostics

The cost of a high update rate is not visible in the code. The Rambla.Diagnostics package makes it visible. It counts the mutations and the notifications, it measures the dispatcher, and it gives recommendations.

dotnet add package Rambla.Diagnostics

Attach a session

using Rambla.Diagnostics;

using DiagnosticsSession session = StateDiagnostics.Attach(viewModel);

// Print the report one time in each second.
Console.WriteLine(session.Snapshot());

A session is an observer. It does not change the behavior of the state. The notification order, the coalescing, the coherence and the error behavior stay the same. Dispose the session to detach it.

The report

DiagnosticsSnapshot.ToString() makes this report:

MarketViewModel
  Incoming state mutations  :   18,420 / sec
  UI notifications          :       58 / sec
  Coalescing                :   99.68 %
  Dispatcher hops           :       60 / sec
  Longest UI flush          :   2.8 ms
  UI thread budget          :     17 %

  ! 'Positions' generated 14,281 notifications/sec. Recommendation: batch related
    writes with BeginUpdate(), or for a collection use Batch()/ReplaceSnapshot().

Include the dispatcher data

The state alone cannot measure the dispatcher. To get the dispatcher latency, the number of hops and a correct UI thread budget, put your scheduler in a DiagnosticsScheduler.

Warning: give the same instance to the state and to Attach. If the two instances are different, all the dispatcher values are zero.

var scheduler = new DiagnosticsScheduler(DispatcherStateScheduler.ForCurrent());
var vm = new MarketViewModel(scheduler);

using DiagnosticsSession session = StateDiagnostics.Attach(vm, scheduler);

Without a DiagnosticsScheduler, the report omits those lines. The other values stay correct.

The values in a snapshot

Member Meaning
MutationsPerSecond The number of value changes in one second.
NotificationsPerSecond The number of notifications in one second.
FlushesPerSecond The number of flushes in one second.
CoalescingRatio The fraction of the mutations that did not become notifications.
LongestFlush The longest flush after the attach operation.
UiThreadBudget The fraction of the time that the UI thread used for the flushes.
DispatcherHopsPerSecond The number of posts that the dispatcher ran in one second.
AverageDispatcherLatency The mean time between the post and the run.
PeakDispatcherLatency The maximum time between the post and the run.
HotProperties The properties with the highest number of notifications.
Recommendations The actions that can decrease the cost.
HasDispatcherMetrics true if a DiagnosticsScheduler gave the dispatcher data.

Change the thresholds

var options = new DiagnosticsOptions
{
    MaxHotProperties = 10,             // default 5
    HotNotificationsPerSecond = 500,   // default 1000
    UiBudgetWarningFraction = 0.3,     // default 0.5
};

using var session = StateDiagnostics.Attach(vm, scheduler, options: options);

Metrics without the diagnostics package

The core package has counters for the full life of a state. They give no rates and no recommendations, but they have a lower cost.

RamblaOptions.Default.CollectMetrics = true;

StateMetrics metrics = vm.Metrics;
Console.WriteLine($"{metrics.Mutations} mutations, {metrics.Notifications} notifications");

The counters are disabled by default. Thus the write operation stays inexpensive.

Clone this wiki locally