< Summary

Information
Class: AmbientServices.ScopedSystemSwitch
Assembly: AmbientServices
File(s): /home/runner/work/AmbientServices/AmbientServices/AmbientServices/Services/ServiceProfiler.cs
Tag: 332_35464845198
Line coverage
100%
Covered lines: 9
Uncovered lines: 0
Coverable lines: 9
Total lines: 161
Line coverage: 100%
Branch coverage
50%
Covered branches: 1
Total branches: 2
Branch coverage: 50%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)50%22100%
Dispose()100%11100%

File(s)

/home/runner/work/AmbientServices/AmbientServices/AmbientServices/Services/ServiceProfiler.cs

#LineLine coverage
 1
 2using System;
 3
 4namespace AmbientServices;
 5
 6/// <summary>
 7/// An interface that abstracts a service profiler notification sink.
 8/// </summary>
 9/// <remarks>
 10/// <pitch>The push side of profiling: implement this to receive every system switch as it happens and accumulate it how
 11/// <pledge>
 12/// <see cref="OnSystemSwitched"/> is called once per switch, after the switch has been applied.  Each call delivers a c
 13/// Calls may arrive concurrently from multiple call contexts and are not guaranteed to be ordered by timestamp across c
 14/// </pledge>
 15/// </remarks>
 16public interface IAmbientServiceProfilerNotificationSink
 17{
 18    /// <summary>
 19    /// Notifies the notification sink that the system has switched.
 20    /// </summary>
 21    /// <remarks>
 22    /// This function will be called whenever the service profiler is told that the currently-processing system has swit
 23    /// Note that the previously-executing system may or may not be revised at this time.
 24    /// Such revisions can be used to distinguish between processing that resulted in success or failure, or other simil
 25    /// </remarks>
 26    /// <param name="newSystemStartStopwatchTimestamp">The stopwatch timestamp when the new system started.</param>
 27    /// <param name="newSystem">The identifier for the system that is starting to run.</param>
 28    /// <param name="oldSystemStartStopwatchTimestamp">The stopwatch timestamp when the old system started running.</par
 29    /// <param name="oldSystem">The identifier for the system that has just finished running, as known to the call conte
 30    /// <param name="revisedOldSystem">An optional revised name for the system that has just finished running that overr
 31    void OnSystemSwitched(long newSystemStartStopwatchTimestamp, string newSystem, long oldSystemStartStopwatchTimestamp
 32}
 33/// <summary>
 34/// An interface that abstracts a service profiler service.
 35/// </summary>
 36/// <remarks>
 37/// <pitch>
 38/// Cheap, always-on attribution of where a request's time goes across backend systems (databases, caches, remote servic
 39/// It deliberately does <em>not</em> measure CPU time, allocations, or call counts at finer than system-switch granular
 40/// </pitch>
 41/// <pledge>
 42/// Within a single call context exactly one system is active at a time: <see cref="SwitchSystem"/> ends the currently-a
 43/// Concurrency is expressed only by multiple call contexts, each internally sequential; an <see cref="System.Threading.
 44/// Registered <see cref="IAmbientServiceProfilerNotificationSink"/> instances are notified on every switch with the ide
 45/// </pledge>
 46/// <priority>
 47/// 1. Always-on affordability over measurement richness: the point of this service is that it can be left enabled in pr
 48/// 2. Self-contained notifications over a narrow notification surface: every switch tells sinks the identities and star
 49/// </priority>
 50/// </remarks>
 51public interface IAmbientServiceProfiler
 52{
 53    /// <summary>
 54    /// Gets the identifier of the system currently active in this call context, or null/empty when the default
 55    /// (unattributed) system is active.  Used by <see cref="ScopedSystemSwitch"/> to capture and restore the prior
 56    /// system on dispose so that switch scopes nest correctly.
 57    /// </summary>
 58    string? CurrentSystem { get; }
 59    /// <summary>
 60    /// Switches the system that is executing in this call context.
 61    /// </summary>
 62    /// <param name="system">A string indicating which system is beginning to execute, or null or empty string to indica
 63    /// <param name="updatedPreviousSystem">An optional updated for the previous system in case part of the system ident
 64    /// <remarks>
 65    /// The system should be identified using the following form:
 66    /// MainSystem/[Subsystem Type:]Subsystem/[Subsystem Type:]Subsystem/[Subsystem Type:]Subsystem/[Subsystem Type:]Sub
 67    /// For example:
 68    /// DynamoDB/Table:My-table/Partition:342644/Result:Success
 69    /// S3/Bucket:My-bucket/Prefix:abcdefg/Result:Retry
 70    /// SQL/Database:My-database/Table:User/Result:Failed
 71    /// In the analysis pipeline, systems are grouped using the system group transform Regex from the settings.
 72    /// Any matching Regex match groups found by the regex expression will be concatenated into the transformed string.
 73    /// To pass the system string through unaltered (as its own group), use null, empty string, or .* as the system grou
 74    /// For example, to transform the group systems by only the main system, database, bucket, and result, while retaini
 75    /// (?:([^:/]+)(?:(/Database:[^:/]*)|(/Bucket:[^:/]*)|(/Result:[^:/]*)|(?:/[^/]*))*)
 76    /// For example, to transform the group systems by only the main system, database, bucket, and result, without retai
 77    /// (?:([^:/]+)(?:(?:(/)(?:Database:)([^:/]*))|(?:(/)(?:Bucket:)([^:/]*))|(?:(/)(?:Result:)([^:/]*))|(?:/[^/]*))*)
 78    /// </remarks>
 79    void SwitchSystem(string? system, string? updatedPreviousSystem = null);
 80    /// <summary>
 81    /// Re-baselines the current call context as a freshly-forked context: the active system becomes the default (null)
 82    /// system starting now, and the interval the context inherited from its parent (the parent's active system, timed
 83    /// since the parent last switched) is deliberately <em>not</em> recorded.
 84    /// </summary>
 85    /// <remarks>
 86    /// Call this at the start of forked work (after execution has actually crossed onto the forked context, e.g. after
 87    /// the first <c>await</c>).  A forked <see cref="System.Threading.ExecutionContext"/> inherits the parent's
 88    /// active-system start timestamp via <see cref="System.Threading.AsyncLocal{T}"/> flow; when many forks share that
 89    /// inherited start, each one's first <see cref="SwitchSystem"/> would close an interval reaching back to the parent
 90    /// start, so the parent's pre-fork time gets charged once per fork and the default group's aggregate (busy) time
 91    /// balloons.  Unlike <see cref="SwitchSystem"/>, this records no interval — it only discards the inherited start, s
 92    /// the parent (which keeps its own copy) still accounts for that span exactly once.  No notification sink is called
 93    /// </remarks>
 94    void ResetForkedCallContext();
 95    /// <summary>
 96    /// Registers a system switch notification sink with this ambient service profiler.
 97    /// </summary>
 98    /// <param name="sink">An <see cref="IAmbientServiceProfilerNotificationSink"/> that will receive notifications when
 99    /// <returns>true if the registration was successful, false if the specified sink was already registered.</returns>
 100    bool RegisterSystemSwitchedNotificationSink(IAmbientServiceProfilerNotificationSink sink);
 101    /// <summary>
 102    /// Deregisters an access notification sink with this ambient service profiler.
 103    /// </summary>
 104    /// <param name="sink">An <see cref="IAmbientServiceProfilerNotificationSink"/> that will receive notifications when
 105    /// <returns>true if the deregistration was successful, false if the specified sink was not registered.</returns>
 106    bool DeregisterSystemSwitchedNotificationSink(IAmbientServiceProfilerNotificationSink sink);
 107#if NET5_0_OR_GREATER
 108    /// <summary>
 109    /// Scopes a switch to a specified system to simplify the syntax of tracking system usage when the full system ident
 110    /// </summary>
 111    /// <param name="system">A string indicating which system is beginning to execute, or null or empty string to indica
 112    /// <param name="updatedPreviousSystem">An optional updated for the previous system in case part of the system ident
 113    /// <returns>A <see cref="ScopedSystemSwitch"/> that should be disposed when usage of the system is finished.</retur
 114    ScopedSystemSwitch ScopedSystemSwitch(string? system, string? updatedPreviousSystem = null)
 115    {
 116        return new ScopedSystemSwitch(this, system, updatedPreviousSystem);
 117    }
 118#endif
 119}
 120/// <summary>
 121/// A disposable scoping class that lets the caller indicate when system usage is complete.
 122/// </summary>
 123/// <remarks>
 124/// <pitch>The ergonomic way to mark a system active for the duration of a <c>using</c> block when the full system ident
 125/// <pledge>Construction captures the currently-active system (via <see cref="IAmbientServiceProfiler.CurrentSystem"/>) 
 126/// <plan>A thin wrapper that holds the <see cref="IAmbientServiceProfiler"/>, the system it switched to, and the system
 127/// </remarks>
 128public sealed class ScopedSystemSwitch : IDisposable
 129{
 130    private readonly IAmbientServiceProfiler _profiler;
 131    private readonly string? _system;
 132    private readonly string? _previousSystem;
 133
 134    /// <summary>
 135    /// Constructs a scoped system switcher.
 136    /// </summary>
 137    /// <param name="profiler">The <see cref="IAmbientServiceProfiler"/> to use.</param>
 138    /// <param name="system">The name of the system that will execute during the scope.</param>
 139    /// <param name="updatedPreviousSystem">An update to the previously-running system in case the full name was not kno
 140    /// <exception cref="ArgumentNullException"><paramref name="profiler"/> is null.</exception>
 2141    public ScopedSystemSwitch(IAmbientServiceProfiler profiler, string? system, string? updatedPreviousSystem = null)
 142    {
 2143        if (profiler == null) throw new ArgumentNullException(nameof(profiler));
 2144        _profiler = profiler;
 2145        _system = system;
 146        // capture the system active before this scope so disposal can restore it, allowing scopes to nest
 2147        _previousSystem = profiler.CurrentSystem;
 2148        profiler.SwitchSystem(system, updatedPreviousSystem);
 2149    }
 150    /// <summary>
 151    /// Disposes of the instance, restoring the system that was active when the scope was constructed.
 152    /// </summary>
 153    public void Dispose()
 154    {
 155        // Restore the previously-active system (so scopes nest) and replay the system we opened as the revised
 156        // previous system so the collector attributes this scope's interval from the self-contained switch event
 157        // instead of the per-context active-system map, which last-writer-wins across fan-out children that share
 158        // an inherited call-context key.
 2159        _profiler.SwitchSystem(_previousSystem, _system);
 2160    }
 161}