< Summary

Information
Class: AmbientServices.AmbientServiceProfilerCoordinator
Assembly: AmbientServices
File(s): /home/runner/work/AmbientServices/AmbientServices/AmbientServices/Helpers/ServiceProfilerHelpers.cs
Tag: 332_35464845198
Line coverage
100%
Covered lines: 42
Uncovered lines: 0
Coverable lines: 42
Total lines: 258
Line coverage: 100%
Branch coverage
100%
Covered branches: 28
Total branches: 28
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
.ctor()100%11100%
.ctor(...)100%44100%
OnSystemSwitched(...)100%22100%
CreateCallContextProfiler(...)100%66100%
CreateTimeWindowProfiler(...)100%44100%
CreateProcessProfiler(...)100%44100%
Dispose(...)100%66100%
Dispose()100%11100%

File(s)

/home/runner/work/AmbientServices/AmbientServices/AmbientServices/Helpers/ServiceProfilerHelpers.cs

#LineLine coverage
 1using AmbientServices.Utilities;
 2using System;
 3using System.Collections.Generic;
 4using System.Text.RegularExpressions;
 5using System.Threading;
 6using System.Threading.Tasks;
 7
 8namespace AmbientServices;
 9
 10/// <summary>
 11/// A class that coordinates service profilers.
 12/// </summary>
 13/// <remarks>
 14/// <pitch>
 15/// The factory you use to turn the raw <see cref="IAmbientServiceProfiler"/> switch stream into actual profiles.  It bu
 16/// </pitch>
 17/// <pledge><see cref="IAmbientServiceProfilerNotificationSink"/></pledge>
 18/// <pledge>
 19/// Returns null from every factory method when there is no ambient <see cref="IAmbientServiceProfiler"/> to observe.  E
 20/// The system-to-group transform is a <see cref="Regex"/> whose successful capture groups are concatenated to form the 
 21/// </pledge>
 22/// <plan>
 23/// Registers itself as a sink on the ambient <see cref="IAmbientServiceProfiler"/> and fans switch events out through a
 24/// </plan>
 25/// </remarks>
 26public class AmbientServiceProfilerCoordinator : IAmbientServiceProfilerNotificationSink, IDisposable
 27{
 228    private static readonly AmbientService<IAmbientSettingsSet> _SettingsSet = Ambient.GetService<IAmbientSettingsSet>()
 229    private static readonly AmbientService<IAmbientServiceProfiler> _AmbientServiceProfiler = Ambient.GetService<IAmbien
 30
 31    private readonly IAmbientSetting<Regex?> _defaultSystemGroupTransformSetting;
 32    private readonly IAmbientServiceProfiler? _eventBroadcaster;
 33    private readonly AsyncLocal<ScopeOnSystemSwitchedDistributor> _scopeDistributor;
 34    private bool _disposedValue;
 35
 36    /// <summary>
 37    /// Constructs an AmbientServiceProfilerCoordinator using settings obtained from the ambient settings set.
 38    /// </summary>
 39    public AmbientServiceProfilerCoordinator()
 240        : this(_SettingsSet.Local)
 41    {
 242    }
 43    /// <summary>
 44    /// Constructs an AmbientServiceProfilerCoordinator using the specified settings set.
 45    /// </summary>
 46    /// <param name="settingsSet"></param>
 247    public AmbientServiceProfilerCoordinator(IAmbientSettingsSet? settingsSet)
 48    {
 249        _defaultSystemGroupTransformSetting = AmbientSettings.GetSettingsSetSetting<Regex?>(settingsSet, nameof(AmbientS
 250            @"A `Regex` string used to transform the system identifier to a group identifier.
 251The regular expression will attempt to match the system identifier, with the values for any matching match groups being 
 252            s => string.IsNullOrEmpty(s) ? null : new Regex(s, RegexOptions.Compiled));
 253        _scopeDistributor = new AsyncLocal<ScopeOnSystemSwitchedDistributor>();
 254        _eventBroadcaster = _AmbientServiceProfiler.Local;
 255        _eventBroadcaster?.RegisterSystemSwitchedNotificationSink(this);
 256    }
 57
 58    /// <summary>
 59    /// Notifies the notification sink that the system has switched.
 60    /// </summary>
 61    /// <remarks>
 62    /// This function will be called whenever the service profiler is told that the currently-processing system has swit
 63    /// Note that the previously-executing system may or may not be revised at this time.
 64    /// Such revisions can be used to distinguish between processing that resulted in success or failure, or other simil
 65    /// </remarks>
 66    /// <param name="newSystemStartStopwatchTimestamp">The stopwatch timestamp when the new system started.</param>
 67    /// <param name="newSystem">The identifier for the system that is starting to run.</param>
 68    /// <param name="oldSystemStartStopwatchTimestamp">The stopwatch timestamp when the old system started running.</par
 69    /// <param name="oldSystem">The identifier for the system that has just finished running, as known to the call conte
 70    /// <param name="revisedOldSystem">An optional revised name for the system that has just finished running that overr
 71    public void OnSystemSwitched(long newSystemStartStopwatchTimestamp, string newSystem, long oldSystemStartStopwatchTi
 72    {
 273        _scopeDistributor.Value ??= new ScopeOnSystemSwitchedDistributor();
 274        _scopeDistributor.Value.OnSystemSwitched(newSystemStartStopwatchTimestamp, newSystem, oldSystemStartStopwatchTim
 275    }
 76    /// <summary>
 77    /// Creates a service profiler which profiles the current call context.
 78    /// </summary>
 79    /// <param name="scopeName">A name of the call context to attach to the analyzer.</param>
 80    /// <param name="overrideSystemGroupTransformRegex">A <see cref="Regex"/> string to transform the system into a syst
 81    /// <returns>A <see cref="IAmbientServiceProfile"/> that will profile systems executed in this call context, or null
 82    public IAmbientServiceProfile? CreateCallContextProfiler(string scopeName, string? overrideSystemGroupTransformRegex
 83    {
 284        IAmbientServiceProfiler? metrics = _AmbientServiceProfiler.Local;
 285        if (metrics != null)
 86        {
 287            Regex? groupTransform = (overrideSystemGroupTransformRegex == null) ? _defaultSystemGroupTransformSetting.Va
 288            _scopeDistributor.Value ??= new ScopeOnSystemSwitchedDistributor();
 289            CallContextServiceProfiler analyzer = new(_scopeDistributor.Value, scopeName, groupTransform);
 290            return analyzer;
 91        }
 292        return null;
 93    }
 94    /// <summary>
 95    /// Creates a service profiler which profiles the entire process in sequential time units of the specified size.
 96    /// </summary>
 97    /// <param name="scopeNamePrefix">A <see cref="TimeSpan"/> indicating the size of the window.</param>
 98    /// <param name="windowPeriod">A <see cref="TimeSpan"/> indicating how often reports are desired.</param>
 99    /// <param name="onWindowComplete">An async delegate that receives a <see cref="IAmbientServiceProfile"/> at the end
 100    /// <param name="overrideSystemGroupTransformRegex">A <see cref="Regex"/> string to transform the processor into a p
 101    /// <returns>A <see cref="IDisposable"/> that scopes the collection of the profiles.</returns>
 102    public IDisposable? CreateTimeWindowProfiler(string scopeNamePrefix, TimeSpan windowPeriod, Func<IAmbientServiceProf
 103    {
 2104        IAmbientServiceProfiler? metrics = _AmbientServiceProfiler.Local;
 2105        if (metrics == null) return null;
 2106        Regex? groupTransform = (overrideSystemGroupTransformRegex == null) ? _defaultSystemGroupTransformSetting.Value 
 2107        TimeWindowServiceProfiler tracker = new(metrics, scopeNamePrefix, windowPeriod, onWindowComplete, groupTransform
 2108        return tracker;
 109    }
 110    /// <summary>
 111    /// Creates a service profiler which profiles the entire process for the entire (remaining) duration of execution.
 112    /// Note that this is only useful to determine the distribution for an entire process from start to finish, which is
 113    /// <see cref="CreateTimeWindowProfiler"/> is a better match in most situations.
 114    /// </summary>
 115    /// <param name="scopeName">A name for the context to attach to the analyzer.</param>
 116    /// <param name="overrideSystemGroupTransformRegex">A <see cref="Regex"/> string to transform the processor into a p
 117    /// <returns>A <see cref="IAmbientServiceProfile"/> containing a service profile for the entire process.  Note that 
 118    /// <remarks>
 119    /// This is different from using <see cref="CreateCallContextProfiler"/> because that will only analyze the call con
 120    /// whereas this will analyze all threads and call contexts in the process.
 121    /// They will produce the same results only for programs where there is only a single call context (no parallelizati
 122    /// </remarks>
 123    public IAmbientServiceProfile? CreateProcessProfiler(string scopeName, string? overrideSystemGroupTransformRegex = n
 124    {
 2125        IAmbientServiceProfiler? metrics = _AmbientServiceProfiler.Local;
 2126        if (metrics != null)
 127        {
 2128            Regex? groupTransform = (overrideSystemGroupTransformRegex == null) ? _defaultSystemGroupTransformSetting.Va
 2129            ProcessOrSingleTimeWindowServiceProfiler tracker = new(metrics, scopeName, groupTransform);
 2130            return tracker;
 131        }
 2132        return null;
 133    }
 134    /// <summary>
 135    /// Disposes of this instance.  May be overridden by derived classes.
 136    /// </summary>
 137    /// <param name="disposing">Whether or not we're disposing (as opposed to finalizing).</param>
 138    protected virtual void Dispose(bool disposing)
 139    {
 2140        if (!_disposedValue)
 141        {
 2142            if (disposing)
 143            {
 144                // TODO: dispose managed state (managed objects)
 2145                _eventBroadcaster?.DeregisterSystemSwitchedNotificationSink(this);
 146            }
 147
 148            // TODO: free unmanaged resources (unmanaged objects) and override finalizer
 149            // TODO: set large fields to null
 2150            _disposedValue = true;
 151        }
 2152    }
 153    /// <summary>
 154    /// Disposes of this instance.
 155    /// </summary>
 156    public void Dispose()
 157    {
 158        // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
 2159        Dispose(disposing: true);
 2160        GC.SuppressFinalize(this);
 2161    }
 162}
 163/// <summary>
 164/// An interface that abstracts an ambient service profile: the per-system breakdown of time spent within a profiled sco
 165/// </summary>
 166/// <remarks>
 167/// <pitch>
 168/// The read side of profiling.  Hand it a scope (a call context, a time window, or a whole process) and it tells you, p
 169/// </pitch>
 170/// <pledge>
 171/// Each system or system group active during the scope appears as exactly one <see cref="AmbientServiceProfilerAccumula
 172/// For each group the profile reports an aggregate measure (the sum of that group's active intervals across every call 
 173/// A scope that spans multiple call contexts (a process, a time window, or an operation and the contexts it forks) merg
 174/// Reading <see cref="ProfilerStatistics"/> is a snapshot and may be read more than once; whether the currently-executi
 175/// </pledge>
 176/// </remarks>
 177public interface IAmbientServiceProfile : IDisposable
 178{
 179    /// <summary>
 180    /// Gets the name of the scope being analyzed.  The scope identifies the scope of the operations that were profiled.
 181    /// </summary>
 182    string ScopeName { get; }
 183    /// <summary>
 184    /// Gets an enumeration of <see cref="AmbientServiceProfilerAccumulator"/> instances indicating the time spent execu
 185    /// </summary>
 186    IEnumerable<AmbientServiceProfilerAccumulator> ProfilerStatistics { get; }
 187}
 188/// <summary>
 189/// A class that accumulates processing count and both time measures for a specific system or system group.  Immutable a
 190/// </summary>
 191/// <remarks>
 192/// <pitch>
 193/// The per-group row reported by an <see cref="IAmbientServiceProfile"/>.  Carries two distinct time measures so a call
 194/// <see cref="TotalStopwatchTicksUsed"/> (aggregate busy time, parallel use counted multiply) and <see cref="CriticalPa
 195/// </pitch>
 196/// <pledge>
 197/// A pure data carrier; it performs no measurement of its own and simply reports what a collector computed.
 198/// The two measures obey <c>CriticalPathStopwatchTicksUsed</c> &lt;= <c>TotalStopwatchTicksUsed</c> for any valid set o
 199/// Their difference (<see cref="TotalStopwatchTicksUsed"/> - <see cref="CriticalPathStopwatchTicksUsed"/>) is the time 
 200/// Both measures are reported in stopwatch ticks; the <c>TimeUsed</c>/<c>CriticalPathTimeUsed</c> properties convert to
 201/// </pledge>
 202/// </remarks>
 203public class AmbientServiceProfilerAccumulator
 204{
 205
 206    /// <summary>
 207    /// Gets the group the accumulator is for.
 208    /// </summary>
 209    public string Group { get; }
 210    /// <summary>
 211    /// Gets the number of times systems in this group were executed.
 212    /// </summary>
 213    public long ExecutionCount { get; }
 214    /// <summary>
 215    /// Gets the aggregate (busy) number of stopwatch ticks used by this system group, summing every execution's interva
 216    /// This is the resource-cost measure.
 217    /// </summary>
 218    public long TotalStopwatchTicksUsed { get; }
 219    /// <summary>
 220    /// Gets the critical-path number of stopwatch ticks during which this system group was in use, counting time when t
 221    /// This is the latency-contribution measure.  Equals <see cref="TotalStopwatchTicksUsed"/> when no executions of th
 222    /// </summary>
 223    public long CriticalPathStopwatchTicksUsed { get; }
 224    /// <summary>
 225    /// Gets the aggregate (busy) amount of time used by this system group.  See <see cref="TotalStopwatchTicksUsed"/>.
 226    /// </summary>
 227    public TimeSpan TimeUsed => new(TimeSpanUtilities.StopwatchTicksToTimeSpanTicks(TotalStopwatchTicksUsed));
 228    /// <summary>
 229    /// Gets the critical-path amount of time during which this system group was in use.  See <see cref="CriticalPathSto
 230    /// </summary>
 231    public TimeSpan CriticalPathTimeUsed => new(TimeSpanUtilities.StopwatchTicksToTimeSpanTicks(CriticalPathStopwatchTic
 232
 233    /// <summary>
 234    /// Constructs an AmbientServiceProfilerAccumulator for the specified system, treating it as a serial group whose cr
 235    /// </summary>
 236    /// <param name="group">The system.</param>
 237    /// <param name="totalStopwatchTicksUsed">The number of stopwatch ticks used by this system.</param>
 238    /// <param name="executionCount">The initial execution count.  Defaults to one.</param>
 239    /// <remarks>This overload assumes no concurrent use within the group; use the overload that takes a separate critic
 240    public AmbientServiceProfilerAccumulator(string group, long totalStopwatchTicksUsed, long executionCount = 1)
 241        : this(group, totalStopwatchTicksUsed, totalStopwatchTicksUsed, executionCount)
 242    {
 243    }
 244    /// <summary>
 245    /// Constructs an AmbientServiceProfilerAccumulator for the specified system with explicit aggregate and critical-pa
 246    /// </summary>
 247    /// <param name="group">The system.</param>
 248    /// <param name="totalStopwatchTicksUsed">The aggregate (busy) number of stopwatch ticks used by this system, with c
 249    /// <param name="criticalPathStopwatchTicksUsed">The critical-path number of stopwatch ticks during which this syste
 250    /// <param name="executionCount">The execution count.</param>
 251    public AmbientServiceProfilerAccumulator(string group, long totalStopwatchTicksUsed, long criticalPathStopwatchTicks
 252    {
 253        Group = group;
 254        ExecutionCount = executionCount;
 255        TotalStopwatchTicksUsed = totalStopwatchTicksUsed;
 256        CriticalPathStopwatchTicksUsed = criticalPathStopwatchTicksUsed;
 257    }
 258}