using System.Diagnostics; using System.Diagnostics.Metrics; namespace ZB.MOM.WW.OtOpcUa.Commons.Observability; /// /// Central + definitions for OtOpcUa. /// All Akka actors, the OPC UA publish path, and the deploy coordinator emit through these /// pre-created instruments so a single OpenTelemetry / Prometheus binding in Host /// catches everything. No exporter is required — instruments are no-op until a listener /// attaches, so tests and dev hosts pay nothing for instrumentation that nobody scrapes. /// /// Instrument names follow the OpenTelemetry semantic convention pattern /// otopcua.<subsystem>.<event>. Subsystem is one of: deploy, driver, /// virtualtag, scriptedalarm, opcua, redundancy. /// public static class OtOpcUaTelemetry { public const string MeterName = "ZB.MOM.WW.OtOpcUa"; public const string ActivitySourceName = "ZB.MOM.WW.OtOpcUa"; /// Singleton all counters/histograms hang off. public static readonly Meter Meter = new(MeterName); /// Singleton used to start spans wrapping deploy/apply/rebuild. public static readonly ActivitySource ActivitySource = new(ActivitySourceName); // ---------------- Deployment / driver-host coordination ---------------- /// Incremented every time DriverHostActor finishes applying a deployment (Ack or Reject). public static readonly Counter DeploymentApplied = Meter.CreateCounter("otopcua.deploy.applied", unit: "{deployment}", description: "Deployments applied by a driver-role node (outcome=ack|reject)."); /// Time from DriverHostActor receiving DispatchDeployment to emitting the ack/reject. public static readonly Histogram DeploymentApplyDurationSec = Meter.CreateHistogram("otopcua.deploy.apply.duration", unit: "s", description: "Driver-role apply latency from DispatchDeployment → Ack/Reject."); /// DriverInstanceActor spawn count (added=new instance; stop=disposed). public static readonly Counter DriverInstanceLifecycle = Meter.CreateCounter("otopcua.driver.lifecycle", unit: "{event}", description: "DriverInstanceActor lifecycle transitions (event=spawn|stop|fault)."); // ---------------- VirtualTag / ScriptedAlarm engines ---------------- public static readonly Counter VirtualTagEval = Meter.CreateCounter("otopcua.virtualtag.eval", unit: "{eval}", description: "Virtual-tag evaluations attempted (outcome=ok|fail|skip)."); public static readonly Counter ScriptedAlarmTransition = Meter.CreateCounter("otopcua.scriptedalarm.transition", unit: "{transition}", description: "Scripted-alarm state transitions (state=active|acknowledged|inactive)."); // ---------------- OPC UA address-space + redundancy ---------------- public static readonly Counter OpcUaSinkWrite = Meter.CreateCounter("otopcua.opcua.sink.write", unit: "{write}", description: "Writes that landed in IOpcUaAddressSpaceSink (kind=value|alarm|rebuild)."); public static readonly Counter ServiceLevelChange = Meter.CreateCounter("otopcua.redundancy.service_level_change", unit: "{change}", description: "OPC UA Server.ServiceLevel transitions emitted by the redundancy state."); // ---------------- Convenience helpers ---------------- /// /// Starts a deploy span tagged with the deployment id. Caller disposes to close. Returns /// null when no listener is attached so the call site stays cheap on undecorated builds. /// public static Activity? StartDeployApplySpan(string deploymentId) { var activity = ActivitySource.StartActivity("otopcua.deploy.apply", ActivityKind.Internal); activity?.SetTag("otopcua.deployment_id", deploymentId); return activity; } /// Span wrapping a full OPC UA address-space rebuild (Phase7 plan → apply). public static Activity? StartAddressSpaceRebuildSpan() => ActivitySource.StartActivity("otopcua.opcua.address_space_rebuild", ActivityKind.Internal); }