feat(runtime): publish DriverHealthChanged via DriverInstanceActor

- IDriverHealthPublisher in Core.Abstractions + NullDriverHealthPublisher
  no-op for tests/dev-stub paths.
- AkkaDriverHealthPublisher in Runtime forwards to the cluster-wide
  `driver-health` DPS topic.
- DriverInstanceActor instrumented to publish snapshots on every
  observable state change + a periodic 30s heartbeat so the AdminUI
  snapshot store warms up for newly-joined SignalR clients.
- Sliding 5-minute Faulted-count tracked per actor via Queue<DateTime>.
- DriverHostActor.SpawnChild threads clusterId (_localNode.Value) and
  the health publisher down to every DriverInstanceActor child.
- ServiceCollectionExtensions.AddOtOpcUaRuntime registers
  AkkaDriverHealthPublisher as IDriverHealthPublisher singleton.
This commit is contained in:
Joseph Doherty
2026-05-28 10:22:44 -04:00
parent 29370fde3c
commit 4203b84d51
5 changed files with 205 additions and 10 deletions
@@ -42,6 +42,7 @@ public static class ServiceCollectionExtensions
services.TryAddSingleton<IDriverFactory>(NullDriverFactory.Instance);
services.TryAddSingleton<IOpcUaAddressSpaceSink>(NullOpcUaAddressSpaceSink.Instance);
services.TryAddSingleton<IServiceLevelPublisher>(NullServiceLevelPublisher.Instance);
services.TryAddSingleton<IDriverHealthPublisher, AkkaDriverHealthPublisher>();
return services;
}
@@ -87,6 +88,7 @@ public static class ServiceCollectionExtensions
var addressSpaceSink = resolver.GetService<IOpcUaAddressSpaceSink>() ?? NullOpcUaAddressSpaceSink.Instance;
var serviceLevel = resolver.GetService<IServiceLevelPublisher>() ?? NullServiceLevelPublisher.Instance;
var loggerFactory = resolver.GetService<ILoggerFactory>() ?? NullLoggerFactory.Instance;
var healthPublisher = resolver.GetService<IDriverHealthPublisher>() ?? NullDriverHealthPublisher.Instance;
var dbHealth = system.ActorOf(
DbHealthProbeActor.Props(dbFactory),
@@ -116,7 +118,8 @@ public static class ServiceCollectionExtensions
DriverHostActor.Props(dbFactory, roleInfo.LocalNode, coordinator: null,
driverFactory: driverFactory, localRoles: roleInfo.LocalRoles,
dependencyMux: mux,
opcUaPublishActor: publishActor),
opcUaPublishActor: publishActor,
healthPublisher: healthPublisher),
DriverHostActorName);
registry.Register<DriverHostActorKey>(driverHost);