| | | 1 | | using AmbientServices.Utilities; |
| | | 2 | | using System; |
| | | 3 | | |
| | | 4 | | namespace AmbientServices; |
| | | 5 | | |
| | | 6 | | /// <summary> |
| | | 7 | | /// An interface that callers implement to receive bottleneck exit notifications. |
| | | 8 | | /// </summary> |
| | | 9 | | /// <remarks> |
| | | 10 | | /// <pitch>The push side of bottleneck detection: implement this to receive every bottleneck access as it completes and |
| | | 11 | | /// <pledge> |
| | | 12 | | /// <see cref="BottleneckExited"/> is called once per access, after the access has ended, with the <see cref="AmbientBot |
| | | 13 | | /// Calls may arrive concurrently from any call context or thread; implementations must be thread-safe unless the regist |
| | | 14 | | /// </pledge> |
| | | 15 | | /// </remarks> |
| | | 16 | | public interface IAmbientBottleneckExitNotificationSink |
| | | 17 | | { |
| | | 18 | | /// <summary> |
| | | 19 | | /// Notification that the bottleneck was exited. |
| | | 20 | | /// </summary> |
| | | 21 | | /// <param name="bottleneckAccessor">The <see cref="AmbientBottleneckAccessor"/> representing the access.</param> |
| | | 22 | | void BottleneckExited(AmbientBottleneckAccessor bottleneckAccessor); |
| | | 23 | | } |
| | | 24 | | /// <summary> |
| | | 25 | | /// An interface that callers implement to receive bottleneck entry notifications. |
| | | 26 | | /// </summary> |
| | | 27 | | /// <remarks> |
| | | 28 | | /// <pitch>The optional companion to <see cref="IAmbientBottleneckExitNotificationSink"/> for sinks that need to see acc |
| | | 29 | | /// <pledge> |
| | | 30 | | /// <see cref="BottleneckEntered"/> is called once per access, when the access begins; the delivered <see cref="AmbientB |
| | | 31 | | /// There is no separate registration for this interface: the detector notifies entries only to registered exit sinks th |
| | | 32 | | /// Calls may arrive concurrently from any call context or thread; implementations must be thread-safe. |
| | | 33 | | /// </pledge> |
| | | 34 | | /// </remarks> |
| | | 35 | | public interface IAmbientBottleneckEnterNotificationSink |
| | | 36 | | { |
| | | 37 | | /// <summary> |
| | | 38 | | /// Notification that the bottleneck was entered. |
| | | 39 | | /// </summary> |
| | | 40 | | /// <param name="bottleneckAccessor">The <see cref="AmbientBottleneckAccessor"/> representing the access.</param> |
| | | 41 | | void BottleneckEntered(AmbientBottleneckAccessor bottleneckAccessor); |
| | | 42 | | } |
| | | 43 | | /// <summary> |
| | | 44 | | /// An interface that abstracts an ambient bottleneck detector. |
| | | 45 | | /// </summary> |
| | | 46 | | /// <remarks> |
| | | 47 | | /// Each time a bottleneck is used, the caller informs this interface, telling it when the bottleneck is entered and exi |
| | | 48 | | /// A survey that ranks the bottlenecks by how close to (or how much over) their limits they are may then be accessed. |
| | | 49 | | /// <pitch> |
| | | 50 | | /// Cheap, always-on measurement of how close the system is to maxing out — saturation of known scalability limits (API |
| | | 51 | | /// It measures only what callers explicitly bracket, and only against limits the caller declares — it does not discover |
| | | 52 | | /// </pitch> |
| | | 53 | | /// <pledge> |
| | | 54 | | /// <see cref="EnterBottleneck"/> begins an access to the specified bottleneck and returns the accessor whose disposal e |
| | | 55 | | /// Registered <see cref="IAmbientBottleneckExitNotificationSink"/> instances are notified once per access when it ends; |
| | | 56 | | /// </pledge> |
| | | 57 | | /// <priority> |
| | | 58 | | /// 1. Always-on affordability over finding bottlenecks by itself: it measures only what callers explicitly bracket, and |
| | | 59 | | /// 2. Accumulating nothing here over sink simplicity: raw accesses are broadcast and every aggregation, windowing, and |
| | | 60 | | /// </priority> |
| | | 61 | | /// </remarks> |
| | | 62 | | public interface IAmbientBottleneckDetector |
| | | 63 | | { |
| | | 64 | | /// <summary> |
| | | 65 | | /// Enters the specified bottleneck. |
| | | 66 | | /// </summary> |
| | | 67 | | /// <param name="bottleneck">The <see cref="AmbientBottleneck"/> for the bottleneck being accessed.</param> |
| | | 68 | | /// <returns>An <see cref="AmbientBottleneckAccessor"/> that should be disposed when exiting the bottleneck.</return |
| | | 69 | | AmbientBottleneckAccessor EnterBottleneck(AmbientBottleneck bottleneck); |
| | | 70 | | /// <summary> |
| | | 71 | | /// Registers an access notification sink with this ambient bottleneck detector. |
| | | 72 | | /// </summary> |
| | | 73 | | /// <param name="sink">An <see cref="IAmbientBottleneckExitNotificationSink"/> that will receive notifications when |
| | | 74 | | /// <returns>true if the registration was successful, false if the specified sink was already registered.</returns> |
| | | 75 | | bool RegisterAccessNotificationSink(IAmbientBottleneckExitNotificationSink sink); |
| | | 76 | | /// <summary> |
| | | 77 | | /// Deregisters an access notification sink with this ambient bottleneck detector. |
| | | 78 | | /// </summary> |
| | | 79 | | /// <param name="sink">The previously-registered <see cref="IAmbientBottleneckExitNotificationSink"/> that received |
| | | 80 | | /// <returns>true if the deregistration was successful, false if the specified sink was not registered.</returns> |
| | | 81 | | bool DeregisterAccessNotificationSink(IAmbientBottleneckExitNotificationSink sink); |
| | | 82 | | } |
| | | 83 | | /// <summary> |
| | | 84 | | /// An enumeration of possible bottleneck types. |
| | | 85 | | /// </summary> |
| | | 86 | | public enum AmbientBottleneckUtilizationAlgorithm |
| | | 87 | | { |
| | | 88 | | /// <summary> |
| | | 89 | | /// Utilization will always be zero, so this type of bottleneck will never rank above any bottlenecks with a non-zer |
| | | 90 | | /// </summary> |
| | | 91 | | Zero, |
| | | 92 | | /// <summary> |
| | | 93 | | /// Utilization will be computed as (<see cref="AmbientBottleneckAccessor.LimitUsed"/> / <see cref="AmbientBottlenec |
| | | 94 | | /// </summary> |
| | | 95 | | Linear, |
| | | 96 | | /// <summary> |
| | | 97 | | /// There are no known fixed limits for this bottleneck, but it should be tracked anyway. |
| | | 98 | | /// Utilization will be computed as 1.0 - 1.0 / (1.0 + Linear Result * 2.0). |
| | | 99 | | /// This formula starts at zero, hits one half when the limit used hits half the limit (relative to the limit window |
| | | 100 | | /// </summary> |
| | | 101 | | ExponentialLimitApproach, |
| | | 102 | | } |
| | | 103 | | /// <summary> |
| | | 104 | | /// A class that tracks a single access to a bottleneck. |
| | | 105 | | /// </summary> |
| | | 106 | | /// <remarks> |
| | | 107 | | /// The access may or may not be in-progress. |
| | | 108 | | /// The access counts the same whether the operation being performed succeeds or not because the contention will be the |
| | | 109 | | /// <pitch>The record of usage against one bottleneck — a single access (dispose it to end the access) or an aggregate o |
| | | 110 | | /// <pledge> |
| | | 111 | | /// For a bottleneck marked <see cref="AmbientBottleneck.Automatic"/>, usage accrues as elapsed stopwatch time and is fi |
| | | 112 | | /// <see cref="Utilization"/> is recomputed only at construction, on each usage report, and at disposal; instances sort |
| | | 113 | | /// Property reads and usage updates are thread-safe; disposal ends the access exactly once and notifies the owning dete |
| | | 114 | | /// </pledge> |
| | | 115 | | /// <plan> |
| | | 116 | | /// Mutable state (end timestamp, access count, limit used, utilization) lives in fields updated with <see cref="System. |
| | | 117 | | /// Utilization derives from the bottleneck's <see cref="AmbientBottleneckUtilizationAlgorithm"/>: Linear compares the r |
| | | 118 | | /// The internal <c>Split</c>/<c>Combine</c> operations exist for windowed surveys: Split clips an access at a window bo |
| | | 119 | | /// </plan> |
| | | 120 | | /// </remarks> |
| | | 121 | | public sealed class AmbientBottleneckAccessor : IComparable<AmbientBottleneckAccessor>, IDisposable |
| | | 122 | | { |
| | | 123 | | private readonly BasicAmbientBottleneckDetector _owner; |
| | | 124 | | private long _accessEndStopwatchTimestamp; // interlocked, long.MaxValue until the access finishes |
| | | 125 | | private long _accessCount; // interlocked, zero until the access finishes |
| | | 126 | | private double _limitUsed; // interlocked, zero until set by Dispose, SetUsage or AddUsage |
| | | 127 | | private double _utilization; // interlocked, zero until set by Dispose, SetUsage or AddUsage |
| | | 128 | | |
| | | 129 | | internal long AccessBeginStopwatchTimestamp { get; } |
| | 0 | 130 | | internal long AccessEndStopwatchTimestamp => _accessEndStopwatchTimestamp; |
| | | 131 | | /// <summary> |
| | | 132 | | /// Gets the <see cref="AmbientBottleneck"/> for the bottleneck. |
| | | 133 | | /// </summary> |
| | | 134 | | public AmbientBottleneck Bottleneck { get; } |
| | | 135 | | /// <summary> |
| | | 136 | | /// Gets the beginning of the time range for the access. |
| | | 137 | | /// </summary> |
| | 2 | 138 | | public DateTime AccessBegin => new(TimeSpanUtilities.StopwatchTimestampToDateTime(AccessBeginStopwatchTimestamp)); |
| | | 139 | | /// <summary> |
| | | 140 | | /// Gets the end of the time range for the access, if the access is finished. |
| | | 141 | | /// </summary> |
| | 2 | 142 | | public DateTime? AccessEnd => (_accessEndStopwatchTimestamp >= long.MaxValue) ? null : new DateTime(TimeSpanUtilitie |
| | | 143 | | /// <summary> |
| | | 144 | | /// Gets the number of times the bottleneck was accessed. This is only statistical and is not used to compute <see |
| | | 145 | | /// </summary> |
| | 2 | 146 | | public long AccessCount => _accessCount; |
| | | 147 | | /// <summary> |
| | | 148 | | /// Gets the amount of the limit which was used. Note that this is in units of <see cref="System.Diagnostics.Stopwa |
| | | 149 | | /// </summary> |
| | 2 | 150 | | public double LimitUsed => _limitUsed; |
| | | 151 | | /// <summary> |
| | | 152 | | /// Gets the utilization factor, usually between 0.0 and 1.0, where 1.0 indicates that the limit was just reached, a |
| | | 153 | | /// </summary> |
| | | 154 | | /// <remarks> |
| | | 155 | | /// This computed value is used to sort the seriousness of the usage, with larger values indicating more of a proble |
| | | 156 | | /// The value is only updated when constructed, when <see cref="SetUsage"/> or <see cref="AddUsage"/> is called, or |
| | | 157 | | /// </remarks> |
| | 2 | 158 | | public double Utilization => _utilization; |
| | | 159 | | /// <summary> |
| | | 160 | | /// Gets number of stopwatch ticks between the beginning and end of the access. |
| | | 161 | | /// </summary> |
| | 2 | 162 | | public long AccessDurationStopwatchTicks => ((_accessEndStopwatchTimestamp >= long.MaxValue) ? AmbientClock.Ticks : |
| | | 163 | | |
| | | 164 | | /// <summary> |
| | | 165 | | /// Constructs a single-access AmbientBottleneckAccessRecord for the specified bottleneck with access starting at th |
| | | 166 | | /// </summary> |
| | 2 | 167 | | internal AmbientBottleneckAccessor(BasicAmbientBottleneckDetector owner, AmbientBottleneck bottleneck, long accessBe |
| | | 168 | | { |
| | 2 | 169 | | _owner = owner ?? throw new ArgumentNullException(nameof(owner)); |
| | 2 | 170 | | Bottleneck = bottleneck ?? throw new ArgumentNullException(nameof(bottleneck)); |
| | 2 | 171 | | AccessBeginStopwatchTimestamp = accessBeginStopwatchTimestamp; |
| | 2 | 172 | | _accessEndStopwatchTimestamp = long.MaxValue; |
| | 2 | 173 | | } |
| | | 174 | | /// <summary> |
| | | 175 | | /// Constructs an AmbientBottleneckAccessRecord from the completely specified property values. |
| | | 176 | | /// </summary> |
| | 2 | 177 | | internal AmbientBottleneckAccessor(BasicAmbientBottleneckDetector owner, AmbientBottleneck bottleneck, long accessBe |
| | | 178 | | { |
| | 2 | 179 | | _owner = owner ?? throw new ArgumentNullException(nameof(owner)); |
| | 2 | 180 | | Bottleneck = bottleneck ?? throw new ArgumentNullException(nameof(bottleneck)); |
| | 2 | 181 | | AccessBeginStopwatchTimestamp = accessBeginStopwatchTimestamp; |
| | 2 | 182 | | _accessEndStopwatchTimestamp = accessEndStopwatchTimestamp; |
| | 2 | 183 | | _accessCount = accessCount; |
| | 2 | 184 | | _limitUsed = limitUsed; |
| | 2 | 185 | | UpdateUtilization(); |
| | 2 | 186 | | } |
| | | 187 | | /// <summary> |
| | | 188 | | /// Constructs a AmbientBottleneckAccessRecord from the specified property values. |
| | | 189 | | /// </summary> |
| | | 190 | | /// <param name="owner">The <see cref="BasicAmbientBottleneckDetector"/> that owns this access.</param> |
| | | 191 | | /// <param name="bottleneck">The <see cref="AmbientBottleneck"/> being accessed.</param> |
| | | 192 | | /// <param name="accessBegin">The <see cref="DateTime"/> indicating the beginning of the access (presumably now, or |
| | 2 | 193 | | internal AmbientBottleneckAccessor(BasicAmbientBottleneckDetector owner, AmbientBottleneck bottleneck, DateTime acce |
| | | 194 | | { |
| | 2 | 195 | | _owner = owner ?? throw new ArgumentNullException(nameof(owner)); |
| | 2 | 196 | | Bottleneck = bottleneck ?? throw new ArgumentNullException(nameof(bottleneck)); |
| | 2 | 197 | | AccessBeginStopwatchTimestamp = TimeSpanUtilities.DateTimeToStopwatchTimestamp(accessBegin.Ticks); |
| | 2 | 198 | | _accessEndStopwatchTimestamp = long.MaxValue; |
| | 2 | 199 | | } |
| | | 200 | | |
| | | 201 | | /// <summary> |
| | | 202 | | /// Sets the access count and limit used. |
| | | 203 | | /// </summary> |
| | | 204 | | /// <param name="accessCount">The number of access counts.</param> |
| | | 205 | | /// <param name="limitUsed">The amount towards the associated limit that has been used.</param> |
| | | 206 | | public void SetUsage(long accessCount, double limitUsed) |
| | | 207 | | { |
| | 2 | 208 | | if (Bottleneck.Automatic) throw new InvalidOperationException("SetUsage cannot be used on Automatic bottlenecks! |
| | 2 | 209 | | System.Threading.Interlocked.Exchange(ref _accessCount, accessCount); |
| | 2 | 210 | | System.Threading.Interlocked.Exchange(ref _limitUsed, limitUsed); |
| | 2 | 211 | | UpdateUtilization(); |
| | 2 | 212 | | } |
| | | 213 | | |
| | | 214 | | /// <summary> |
| | | 215 | | /// Adds to the access count and limit used. |
| | | 216 | | /// </summary> |
| | | 217 | | /// <param name="additionalAccessCount">The additional number of access counts.</param> |
| | | 218 | | /// <param name="additionalLimitUsed">The additional amount towards the associated limit that has been used.</param> |
| | | 219 | | public void AddUsage(long additionalAccessCount, double additionalLimitUsed) |
| | | 220 | | { |
| | 2 | 221 | | if (Bottleneck.Automatic) throw new InvalidOperationException("SetUsage cannot be used on Automatic bottlenecks! |
| | 2 | 222 | | System.Threading.Interlocked.Add(ref _accessCount, additionalAccessCount); |
| | 2 | 223 | | InterlockedUtilities.TryOptimisticAdd(ref _limitUsed, additionalLimitUsed); |
| | 2 | 224 | | UpdateUtilization(); |
| | 2 | 225 | | } |
| | | 226 | | |
| | | 227 | | private void UpdateUtilization() |
| | | 228 | | { |
| | 2 | 229 | | System.Threading.Interlocked.Exchange(ref _utilization, ComputeUtilization(Bottleneck.UtilizationAlgorithm, _acc |
| | 2 | 230 | | } |
| | | 231 | | |
| | | 232 | | private static double ComputeUtilization(AmbientBottleneckUtilizationAlgorithm limitType, long totalStopwatchTicks, |
| | | 233 | | { |
| | 2 | 234 | | if (limit == null || Math.Abs(limit.Value) < 1) limit = 1.0; |
| | 2 | 235 | | double limitPeriodStopwatchTicks = (limitPeriod == null) ? 1.0 : TimeSpanUtilities.TimeSpanTicksToStopwatchTicks |
| | 2 | 236 | | if (totalStopwatchTicks < 1) totalStopwatchTicks = 1; |
| | 2 | 237 | | double result = limitType switch { |
| | 2 | 238 | | AmbientBottleneckUtilizationAlgorithm.Linear => (1.0 * limitUsed / totalStopwatchTicks) / (1.0 * limit.Value |
| | 2 | 239 | | AmbientBottleneckUtilizationAlgorithm.ExponentialLimitApproach => 1.0 - 1.0 / (2.0 * limitUsed / totalStopwa |
| | 2 | 240 | | _ => 0.0, |
| | 2 | 241 | | }; |
| | 2 | 242 | | return result; |
| | | 243 | | } |
| | | 244 | | |
| | | 245 | | /// <summary> |
| | | 246 | | /// Disposes of this instance, indicating that the access of the bottleneck has completed. |
| | | 247 | | /// </summary> |
| | | 248 | | public void Dispose() |
| | | 249 | | { |
| | 2 | 250 | | System.Threading.Interlocked.Exchange(ref _accessEndStopwatchTimestamp, AmbientClock.Ticks); |
| | 2 | 251 | | if (Bottleneck.Automatic && _accessCount == 0) |
| | | 252 | | { |
| | 2 | 253 | | System.Threading.Interlocked.Exchange(ref _accessCount, 1); |
| | 2 | 254 | | System.Threading.Interlocked.Exchange(ref _limitUsed, _accessEndStopwatchTimestamp - AccessBeginStopwatchTim |
| | | 255 | | } |
| | 2 | 256 | | UpdateUtilization(); |
| | 2 | 257 | | _owner.LeaveBottleneck(this); |
| | 2 | 258 | | GC.SuppressFinalize(this); |
| | 2 | 259 | | } |
| | | 260 | | |
| | | 261 | | internal (AmbientBottleneckAccessor, AmbientBottleneckAccessor?) Split(long oldWindowBeginStopwatchTicks, long split |
| | | 262 | | { |
| | | 263 | | long accessCount; |
| | | 264 | | double limitUsedSoFar; |
| | 2 | 265 | | if (Bottleneck.Automatic) |
| | | 266 | | { |
| | 2 | 267 | | accessCount = _accessCount; |
| | 2 | 268 | | limitUsedSoFar = ((_accessEndStopwatchTimestamp >= long.MaxValue) ? splitStopwatchTicks : _accessEndStopwatc |
| | 2 | 269 | | - AccessBeginStopwatchTimestamp - windowStartLimitUsage; |
| | | 270 | | } |
| | | 271 | | else |
| | | 272 | | { |
| | 2 | 273 | | accessCount = _accessCount - windowStartAccessCount; |
| | 2 | 274 | | limitUsedSoFar = _limitUsed - windowStartLimitUsage; |
| | | 275 | | } |
| | | 276 | | // the old part will be the usage that has occurred since the last window started and will be clipped to the old |
| | 2 | 277 | | AmbientBottleneckAccessor oldPart = new( _owner, Bottleneck, |
| | 2 | 278 | | Math.Max(AccessBeginStopwatchTimestamp, oldWindowBeginStopwatchTicks), splitStopwatchTicks, |
| | 2 | 279 | | accessCount, limitUsedSoFar); |
| | | 280 | | // is this entry *not* still open? // the entry is *not* still open, but it could have span multiple windows ea |
| | 2 | 281 | | if (_accessEndStopwatchTimestamp < long.MaxValue) return (oldPart, null); |
| | | 282 | | // else the new part will exist, and it will use the fork time as the start |
| | 2 | 283 | | AmbientBottleneckAccessor newPart = new(_owner, Bottleneck, splitStopwatchTicks, splitStopwatchTicks, 0, 0); |
| | 2 | 284 | | return (oldPart, newPart); |
| | | 285 | | } |
| | | 286 | | internal AmbientBottleneckAccessor Combine(AmbientBottleneckAccessor that) |
| | | 287 | | { |
| | 2 | 288 | | if (Bottleneck != that.Bottleneck) throw new ArgumentException("The bottlenecks must be the same in order to com |
| | 2 | 289 | | return Combine(that.AccessBeginStopwatchTimestamp, that._accessEndStopwatchTimestamp, that._accessCount, that._l |
| | | 290 | | } |
| | | 291 | | internal AmbientBottleneckAccessor Combine(long accessBeginTicks, long accessEndTicks, long accessCount, double limi |
| | | 292 | | { |
| | 2 | 293 | | long beginTicks = Math.Min(AccessBeginStopwatchTimestamp, accessBeginTicks); |
| | 2 | 294 | | long endTicks = Math.Max(_accessEndStopwatchTimestamp, accessEndTicks); |
| | 2 | 295 | | double sumLimitUsed = _limitUsed + limitUsed; |
| | | 296 | | // if the access hasn't finished, compute everything as if it finished right now |
| | 2 | 297 | | long totalStopwatchTicks = (endTicks >= long.MaxValue) ? (AmbientClock.Ticks - beginTicks) : (endTicks - beginTi |
| | 2 | 298 | | if (totalStopwatchTicks == 0) totalStopwatchTicks = 1; // make sure total ticks is not zero to avoid a divide |
| | 2 | 299 | | double limitProportionUsed = // if the usage is equal to the limit and the range is equal to the period, 1.0 sho |
| | 2 | 300 | | ComputeUtilization(Bottleneck.UtilizationAlgorithm, totalStopwatchTicks, sumLimitUsed, Bottleneck.Limit, Bot |
| | 2 | 301 | | return new AmbientBottleneckAccessor(_owner, Bottleneck, beginTicks) |
| | 2 | 302 | | { |
| | 2 | 303 | | _accessEndStopwatchTimestamp = endTicks, |
| | 2 | 304 | | _accessCount = accessCount, |
| | 2 | 305 | | _limitUsed = limitUsed, |
| | 2 | 306 | | _utilization = limitProportionUsed |
| | 2 | 307 | | }; |
| | | 308 | | } |
| | | 309 | | |
| | | 310 | | /// <summary> |
| | | 311 | | /// Compares this AmbientBottleneckAccessRecord to another one to see which one has used more towards any set limit. |
| | | 312 | | /// </summary> |
| | | 313 | | /// <param name="other">The other AmbientBottleneckAccessRecord.</param> |
| | | 314 | | /// <returns>>0 if this one has used more than <paramref name="other"/>, <0 if this one has used less than <pa |
| | | 315 | | public int CompareTo(AmbientBottleneckAccessor? other) |
| | | 316 | | { |
| | 2 | 317 | | if (other is null) return 1; |
| | 2 | 318 | | int diff = _utilization.CompareTo(other._utilization); |
| | 2 | 319 | | if (diff != 0) return diff; |
| | 2 | 320 | | diff = _limitUsed.CompareTo(other._limitUsed); |
| | 2 | 321 | | if (diff != 0) return diff; |
| | 2 | 322 | | return AccessCount.CompareTo(other.AccessCount); |
| | | 323 | | } |
| | | 324 | | /// <summary> |
| | | 325 | | /// Gets whether or not the specified object is logically equivalent to this one. |
| | | 326 | | /// </summary> |
| | | 327 | | /// <param name="obj">The object to compare to.</param> |
| | | 328 | | /// <returns>true if the objects are logically equivalent, false if they are not.</returns> |
| | | 329 | | public override bool Equals(object? obj) |
| | | 330 | | { |
| | 2 | 331 | | if (ReferenceEquals(this, obj)) return true; |
| | 2 | 332 | | if (obj is not AmbientBottleneckAccessor that) return false; |
| | 2 | 333 | | return Bottleneck.Equals(that.Bottleneck) && AccessBeginStopwatchTimestamp.Equals(that.AccessBeginStopwatchTimes |
| | | 334 | | } |
| | | 335 | | /// <summary> |
| | | 336 | | /// Gets a 32-bit hash code for the value of this object. |
| | | 337 | | /// </summary> |
| | | 338 | | /// <returns>A 32-bit hash code for the value of this object.</returns> |
| | | 339 | | public override int GetHashCode() |
| | | 340 | | { |
| | 2 | 341 | | return Bottleneck.GetHashCode() ^ AccessBeginStopwatchTimestamp.GetHashCode() ^ _accessEndStopwatchTimestamp.Get |
| | | 342 | | } |
| | | 343 | | |
| | | 344 | | /// <summary> |
| | | 345 | | /// Checks to see if two AmbientBottleneckAccessRecord are logically equal. |
| | | 346 | | /// </summary> |
| | | 347 | | /// <param name="left">The left AmbientBottleneckAccessRecord.</param> |
| | | 348 | | /// <param name="right">The right AmbientBottleneckAccessRecord.</param> |
| | | 349 | | /// <returns>true if the AmbientBottleneckAccessRecords are logically equal, false if they are not.</returns> |
| | | 350 | | public static bool operator ==(AmbientBottleneckAccessor? left, AmbientBottleneckAccessor? right) |
| | | 351 | | { |
| | 2 | 352 | | if (left is null) return right is null; |
| | 2 | 353 | | return left.Equals(right); |
| | | 354 | | } |
| | | 355 | | /// <summary> |
| | | 356 | | /// Checks to see if two AmbientBottleneckAccessRecord are logically not equal. |
| | | 357 | | /// </summary> |
| | | 358 | | /// <param name="left">The left AmbientBottleneckAccessRecord.</param> |
| | | 359 | | /// <param name="right">The right AmbientBottleneckAccessRecord.</param> |
| | | 360 | | /// <returns>true if the AmbientBottleneckAccessRecords are logically not equal, false if they are logically equal.< |
| | | 361 | | public static bool operator !=(AmbientBottleneckAccessor? left, AmbientBottleneckAccessor? right) |
| | | 362 | | { |
| | 2 | 363 | | return !(left == right); |
| | | 364 | | } |
| | | 365 | | /// <summary> |
| | | 366 | | /// Checks to see if a AmbientBottleneckAccessRecord is less than another one. |
| | | 367 | | /// </summary> |
| | | 368 | | /// <param name="left">The left AmbientBottleneckAccessRecord.</param> |
| | | 369 | | /// <param name="right">The right AmbientBottleneckAccessRecord.</param> |
| | | 370 | | /// <returns>true if the <paramref name="left"/> is less than <paramref name="right"/>, false if not.</returns> |
| | | 371 | | public static bool operator <(AmbientBottleneckAccessor? left, AmbientBottleneckAccessor? right) |
| | | 372 | | { |
| | 2 | 373 | | return left is null ? right is not null : left.CompareTo(right) < 0; |
| | | 374 | | } |
| | | 375 | | /// <summary> |
| | | 376 | | /// Checks to see if a AmbientBottleneckAccessRecord is less than or equal to another one. |
| | | 377 | | /// </summary> |
| | | 378 | | /// <param name="left">The left AmbientBottleneckAccessRecord.</param> |
| | | 379 | | /// <param name="right">The right AmbientBottleneckAccessRecord.</param> |
| | | 380 | | /// <returns>true if the <paramref name="left"/> is less than or equal to <paramref name="right"/>, false if not.</r |
| | | 381 | | public static bool operator <=(AmbientBottleneckAccessor? left, AmbientBottleneckAccessor? right) |
| | | 382 | | { |
| | 2 | 383 | | return left is null || left.CompareTo(right) <= 0; |
| | | 384 | | } |
| | | 385 | | /// <summary> |
| | | 386 | | /// Checks to see if a AmbientBottleneckAccessRecord is greater than another one. |
| | | 387 | | /// </summary> |
| | | 388 | | /// <param name="left">The left AmbientBottleneckAccessRecord.</param> |
| | | 389 | | /// <param name="right">The right AmbientBottleneckAccessRecord.</param> |
| | | 390 | | /// <returns>true if the <paramref name="left"/> is greater than <paramref name="right"/>, false if not.</returns> |
| | | 391 | | public static bool operator >(AmbientBottleneckAccessor? left, AmbientBottleneckAccessor? right) |
| | | 392 | | { |
| | 2 | 393 | | return left is not null && left.CompareTo(right) > 0; |
| | | 394 | | } |
| | | 395 | | /// <summary> |
| | | 396 | | /// Checks to see if a AmbientBottleneckAccessRecord is greater than or equal to another one. |
| | | 397 | | /// </summary> |
| | | 398 | | /// <param name="left">The left AmbientBottleneckAccessRecord.</param> |
| | | 399 | | /// <param name="right">The right AmbientBottleneckAccessRecord.</param> |
| | | 400 | | /// <returns>true if the <paramref name="left"/> is greater than or equal to <paramref name="right"/>, false if not. |
| | | 401 | | public static bool operator >=(AmbientBottleneckAccessor? left, AmbientBottleneckAccessor? right) |
| | | 402 | | { |
| | 2 | 403 | | return left is null ? right is null : left.CompareTo(right) >= 0; |
| | | 404 | | } |
| | | 405 | | } |