diff --git a/docs/events/projections/async-daemon.md b/docs/events/projections/async-daemon.md index 1963ce7127..ba02bf4156 100644 --- a/docs/events/projections/async-daemon.md +++ b/docs/events/projections/async-daemon.md @@ -499,6 +499,33 @@ using multi-tenancy through a database per tenant. On these spans will be these There is also a counter metric called `marten.daemon.skipping` or `marten.[database name].daemon.skipping` that just emits and update every time that Marten has to "skip" stale events. +## Extended Progression Tracking + +When you opt into extended progression tracking, Marten adds six monitoring +columns (`heartbeat`, `agent_status`, `pause_reason`, `running_on_node`, +`warning_behind_threshold`, `critical_behind_threshold`) to `mt_event_progression` +and the async daemon writes them from runtime state. The shard-state selector +reads them back into `ShardState` so monitoring tooling such as CritterWatch can +display per-shard health. + +```cs +opts.Events.EnableExtendedProgressionTracking = true; +``` + +Marten 9.7 also implements the storage-agnostic +[`IEventStoreInstrumentation`](https://github.com/JasperFx/jasperfx/blob/main/src/JasperFx.Events/IEventStoreInstrumentation.cs) +abstraction from JasperFx.Events 2.9.0 — `Wolverine.CritterWatch.Marten` and +similar satellite packages flip the same toggle via the interface without +referencing Marten's concrete `EventGraph`: + +```cs +((IEventStoreInstrumentation)opts.Events).ExtendedProgressionEnabled = true; +``` + +Both names refer to the same underlying field. New code is encouraged to prefer +the interface property; `EnableExtendedProgressionTracking` continues to work +without deprecation warnings. + ## Advanced Skipping Tracking ::: info diff --git a/src/CoreTests/Events/EventGraph_IEventStoreInstrumentation.cs b/src/CoreTests/Events/EventGraph_IEventStoreInstrumentation.cs new file mode 100644 index 0000000000..24cd1eef5a --- /dev/null +++ b/src/CoreTests/Events/EventGraph_IEventStoreInstrumentation.cs @@ -0,0 +1,75 @@ +using JasperFx.Events; +using Marten.Events; +using Shouldly; +using Xunit; + +namespace CoreTests.Events; + +// #4686 — EventGraph implements JasperFx.Events.IEventStoreInstrumentation (shipped in +// JasperFx 2.9.0, jasperfx#424). Both the legacy EnableExtendedProgressionTracking property +// and the storage-agnostic ExtendedProgressionEnabled name on IEventStoreInstrumentation must +// alias the same underlying field, so Wolverine.CritterWatch.Marten can flip the toggle via +// the abstraction without breaking existing code that sets EnableExtendedProgressionTracking +// directly. +public class EventGraph_IEventStoreInstrumentation +{ + private static EventGraph BuildEventGraph() + { + // EventGraph requires a StoreOptions to wire up its registry, but a default + // StoreOptions is sufficient here -- no connection or schema interaction. + return new EventGraph(new Marten.StoreOptions()); + } + + [Fact] + public void implements_event_store_instrumentation() + { + BuildEventGraph().ShouldBeAssignableTo(); + } + + [Fact] + public void default_is_disabled() + { + var events = BuildEventGraph(); + + events.EnableExtendedProgressionTracking.ShouldBeFalse(); + events.ExtendedProgressionEnabled.ShouldBeFalse(); + ((IEventStoreInstrumentation)events).ExtendedProgressionEnabled.ShouldBeFalse(); + } + + [Fact] + public void enabling_via_legacy_name_flows_to_interface_property() + { + var events = BuildEventGraph(); + var instrumentation = (IEventStoreInstrumentation)events; + + events.EnableExtendedProgressionTracking = true; + + events.ExtendedProgressionEnabled.ShouldBeTrue(); + instrumentation.ExtendedProgressionEnabled.ShouldBeTrue(); + } + + [Fact] + public void enabling_via_interface_flows_to_legacy_name() + { + var events = BuildEventGraph(); + var instrumentation = (IEventStoreInstrumentation)events; + + instrumentation.ExtendedProgressionEnabled = true; + + events.EnableExtendedProgressionTracking.ShouldBeTrue(); + events.ExtendedProgressionEnabled.ShouldBeTrue(); + } + + [Fact] + public void disabling_via_either_surface_unwinds_the_other() + { + var events = BuildEventGraph(); + var instrumentation = (IEventStoreInstrumentation)events; + + events.EnableExtendedProgressionTracking = true; + instrumentation.ExtendedProgressionEnabled = false; + + events.EnableExtendedProgressionTracking.ShouldBeFalse(); + events.ExtendedProgressionEnabled.ShouldBeFalse(); + } +} diff --git a/src/Marten/Events/EventGraph.cs b/src/Marten/Events/EventGraph.cs index 850d76c475..134894bed8 100644 --- a/src/Marten/Events/EventGraph.cs +++ b/src/Marten/Events/EventGraph.cs @@ -41,7 +41,8 @@ namespace Marten.Events; Justification = "Class-level: uses Type.MakeGenericType / MethodInfo.MakeGenericMethod / Activator.CreateInstance / FastExpressionCompiler — runtime code generation. AOT consumers pre-generate codegen artifacts (codegen write) and supply source-generator-backed serializer impls per the AOT publishing guide.")] public partial class EventGraph: EventRegistry, IEventStoreOptions, IReadOnlyEventStoreOptions, IDisposable, IAsyncDisposable, - IAggregationSourceFactory, IDescribeMyself + IAggregationSourceFactory, IDescribeMyself, + IEventStoreInstrumentation { private readonly Cache _aggregateNameByType = new(type => type.IsGenericType ? type.ShortNameInCode() : type.Name.ToTableAlias()); @@ -232,10 +233,30 @@ public override IEvent BuildEvent(object eventData) public bool EnableEventSkippingInProjectionsOrSubscriptions { get; set; } /// - /// When enabled, adds heartbeat, agent_status, pause_reason, and running_on_node - /// columns to the event progression table for CritterWatch monitoring + /// When enabled, adds heartbeat, agent_status, pause_reason, running_on_node, and + /// warning/critical-behind-threshold columns to the event progression table for + /// CritterWatch monitoring. + /// + /// This is the long-standing Marten-side toggle; #4686 added the storage-agnostic + /// as a sibling so + /// store-agnostic monitoring tooling (e.g. Wolverine.CritterWatch.Marten) can flip + /// the switch via the JasperFx.Events abstraction without referencing Marten's concrete + /// option type. Both names refer to the same underlying field; new code is encouraged to + /// prefer ExtendedProgressionEnabled on the interface. + /// /// - public bool EnableExtendedProgressionTracking { get; set; } + public bool EnableExtendedProgressionTracking + { + get => ExtendedProgressionEnabled; + set => ExtendedProgressionEnabled = value; + } + + /// + /// -- the primary + /// (storage-agnostic) toggle that controls extended progression tracking. Aliased by the + /// legacy property. + /// + public bool ExtendedProgressionEnabled { get; set; } public bool UseArchivedStreamPartitioning { get; set; } public bool UseListenNotifyForEventAppends { get; set; }