using System.Runtime.CompilerServices; using MxGateway.Contracts.Proto; namespace ZB.MOM.WW.OtOpcUa.Driver.Galaxy.Runtime; /// /// PR 6.1 — Decorator that emits one span /// per gw subscription RPC. Wraps the production ; /// tests substitute a fake at the same seam without taking the tracing overhead. /// internal sealed class TracedGalaxySubscriber(IGalaxySubscriber inner, string clientName) : IGalaxySubscriber { public async Task> SubscribeBulkAsync( IReadOnlyList fullReferences, int bufferedUpdateIntervalMs, CancellationToken cancellationToken) { using var activity = GalaxyTelemetry.ActivitySource.StartActivity("galaxy.subscribe_bulk"); activity?.SetTag("galaxy.client", clientName); activity?.SetTag("galaxy.tag_count", fullReferences.Count); activity?.SetTag("galaxy.buffered_interval_ms", bufferedUpdateIntervalMs); try { var results = await inner.SubscribeBulkAsync(fullReferences, bufferedUpdateIntervalMs, cancellationToken) .ConfigureAwait(false); activity?.SetTag("galaxy.success_count", results.Count(r => r.WasSuccessful)); return results; } catch (Exception ex) { activity.RecordError(ex); throw; } } public async Task UnsubscribeBulkAsync(IReadOnlyList itemHandles, CancellationToken cancellationToken) { using var activity = GalaxyTelemetry.ActivitySource.StartActivity("galaxy.unsubscribe_bulk"); activity?.SetTag("galaxy.client", clientName); activity?.SetTag("galaxy.tag_count", itemHandles.Count); try { await inner.UnsubscribeBulkAsync(itemHandles, cancellationToken).ConfigureAwait(false); } catch (Exception ex) { activity.RecordError(ex); throw; } } /// /// Streaming RPC — one parent span covers the entire stream lifetime. Per-event /// spans would dominate the trace volume at 50k tags / 1Hz; ops gets per-event /// visibility through 's metrics in PR 6.2 instead. /// public async IAsyncEnumerable StreamEventsAsync( [EnumeratorCancellation] CancellationToken cancellationToken) { using var activity = GalaxyTelemetry.ActivitySource.StartActivity("galaxy.stream_events"); activity?.SetTag("galaxy.client", clientName); IAsyncEnumerator? enumerator = null; try { enumerator = inner.StreamEventsAsync(cancellationToken).GetAsyncEnumerator(cancellationToken); var eventCount = 0L; while (true) { bool moveNext; try { moveNext = await enumerator.MoveNextAsync().ConfigureAwait(false); } catch (Exception ex) { activity.RecordError(ex); activity?.SetTag("galaxy.event_count", eventCount); throw; } if (!moveNext) break; eventCount++; yield return enumerator.Current; } activity?.SetTag("galaxy.event_count", eventCount); } finally { if (enumerator is not null) await enumerator.DisposeAsync().ConfigureAwait(false); } } }