< Summary

Information
Class: AmbientServices.IAmbientStatisticsExtensions
Assembly: AmbientServices
File(s): /home/runner/work/AmbientServices/AmbientServices/AmbientServices/Services/Statistics.cs
Tag: 332_35464845198
Line coverage
100%
Covered lines: 49
Uncovered lines: 0
Coverable lines: 49
Total lines: 545
Line coverage: 100%
Branch coverage
85%
Covered branches: 41
Total branches: 48
Branch coverage: 85.4%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
SetValue(...)100%22100%
SetValue(...)100%22100%
SetValue(...)100%22100%
PreferredTemporalAggregation(...)50%44100%
PreferredSpatialAggregation(...)50%44100%
Aggregate(...)83.33%1212100%
Average(...)100%44100%
SumWithCutoff(...)87.5%88100%
DefaultTemporalAggregation(...)100%55100%
DefaultSpatialAggregation(...)100%55100%

File(s)

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

#LineLine coverage
 1using System;
 2using System.Collections.Generic;
 3using System.Linq;
 4
 5namespace AmbientServices;
 6
 7#pragma warning disable CA1510
 8/// <summary>
 9/// An interface to create and manage system statistics, which provide long-lived high-performance tracking of accumulat
 10/// Statistics can be used to track memory allocated, time waited, minimum or maximum sizes or times, request processing
 11/// Ratios of two statistics can be used to track things like average sizes or times, events per second, bytes per secon
 12/// All times are in terms of ticks whose frequency is <see cref="System.Diagnostics.Stopwatch.Frequency"/>.
 13/// </summary>
 14/// <remarks>
 15/// <pitch>
 16/// The registry answering "how well are the various systems functioning under how much load?" — long-lived, high-perfor
 17/// It holds only the current raw value of each statistic — sampling over time, graphing, and cross-system roll-up are l
 18/// </pitch>
 19/// <pledge>
 20/// Statistics are identified by dash-delimited IDs and live until removed: a get-or-add call returns the already-regist
 21/// Each statistic carries immutable metadata (name, description, units, fixed-floating-point adjustment, aggregation pr
 22/// A built-in execution-time statistic always exists, cannot be removed, and serves as the standard denominator for rat
 23/// All operations are thread-safe and safe to call from any call context.
 24/// </pledge>
 25/// <priority>
 26/// 1. Correct aggregation across servers over convenience at the reporting site: ratios are published as descriptors na
 27/// 2. Update cost over read richness: only the current raw value of each statistic is held, with sampling over time, gr
 28/// </priority>
 29/// </remarks>
 30public interface IAmbientStatistics
 31{
 32    /// <summary>
 33    /// Gets the built-in execution time statistic.
 34    /// </summary>
 35    public IAmbientStatisticReader ExecutionTime { get; }
 36    /// <summary>
 37    /// Gets a <see cref="IDictionary{TKey, TValue}"/> with all the statistics.
 38    /// </summary>
 39    IDictionary<string, IAmbientStatisticReader> Statistics { get; }
 40    /// <summary>
 41    /// Gets a <see cref="IDictionary{TKey, TValue}"/> with all the ratio statistics.
 42    /// </summary>
 43    IDictionary<string, IAmbientRatioStatistic> RatioStatistics { get; }
 44    /// <summary>
 45    /// Finds the specified statistic.
 46    /// </summary>
 47    /// <returns>A <see cref="IAmbientStatisticReader"/> the caller can use to read the statistic, or null if there is n
 48    IAmbientStatisticReader? ReadStatistic(string id);
 49    /// <summary>
 50    /// Adds or updates a time-based statistic with the specified identifier, description, and properties.
 51    /// Time-based statistics are always in seconds.
 52    /// </summary>
 53    /// <param name="type">The <see cref="AmbientStatisticType"/> for the statistic.</param>
 54    /// <param name="id">A dash-delimited identifier for the statistic.</param>
 55    /// <param name="name">A name for the statistic, presumably to use as a chart title.</param>
 56    /// <param name="description">A human-readable description for the statistic.</param>
 57    /// <param name="replaceIfAlreadyExists">true to use a new statistic even if one already exists, false to return an 
 58    /// <param name="initialValue">The initial value for the statistic, if it is created.</param>
 59    /// <param name="minimumValue">An optional value indicating the minimum possible value, if applicable.</param>
 60    /// <param name="maximumValue">An optional value indicating the maximum possible value, if applicable.</param>
 61    /// <param name="temporalAggregationTypes">A set of <see cref="AggregationTypes"/> indicating how this statistic sho
 62    /// <param name="spatialAggregationTypes">A set of <see cref="AggregationTypes"/> indicating how this statistic shou
 63    /// <param name="preferredTemporalAggregationType">A single <see cref="AggregationTypes"/> indicating the default wa
 64    /// <param name="preferredSpatialAggregationType">A single <see cref="AggregationTypes"/> indicating the default way
 65    /// <param name="missingSampleHandling">A <see cref="MissingSampleHandling"/> indicating how clients should treat mi
 66    /// <returns>An <see cref="IAmbientStatistic"/> the caller can use to update the statistic samples.</returns>
 67    IAmbientStatistic GetOrAddTimeBasedStatistic(AmbientStatisticType type, string id, string name, string description, 
 68        , long initialValue = 0, long? minimumValue = null, long? maximumValue = null
 69        , AggregationTypes temporalAggregationTypes = AggregationTypes.None
 70        , AggregationTypes spatialAggregationTypes = AggregationTypes.None
 71        , AggregationTypes preferredTemporalAggregationType = AggregationTypes.None
 72        , AggregationTypes preferredSpatialAggregationType = AggregationTypes.None
 73        , MissingSampleHandling missingSampleHandling = MissingSampleHandling.LinearEstimation
 74        );
 75    /// <summary>
 76    /// Adds or updates a statistic with the specified identifier, description, and properties.
 77    /// </summary>
 78    /// <param name="type">The <see cref="AmbientStatisticType"/> for the statistic.</param>
 79    /// <param name="id">A dash-delimited identifier for the statistic.</param>
 80    /// <param name="name">A name for the statistic, presumably to use as a chart title.</param>
 81    /// <param name="description">A human-readable description for the statistic.</param>
 82    /// <param name="replaceIfAlreadyExists">true to use a new statistic even if one already exists, false to return an 
 83    /// <param name="initialValue">The initial value for the statistic, if it is created.</param>
 84    /// <param name="minimumValue">An optional value indicating the minimum possible value, if applicable.</param>
 85    /// <param name="maximumValue">An optional value indicating the maximum possible value, if applicable.</param>
 86    /// <param name="units">An optional string describing the units of the statistic (after the decimal point is adjuste
 87    /// <param name="fixedFloatingPointAdjustment">An optional value to divide raw samples by to get the floating-point 
 88    /// <param name="temporalAggregationTypes">A set of <see cref="AggregationTypes"/> indicating how this statistic sho
 89    /// <param name="spatialAggregationTypes">A set of <see cref="AggregationTypes"/> indicating how this statistic shou
 90    /// <param name="preferredTemporalAggregationType">A single <see cref="AggregationTypes"/> indicating the default wa
 91    /// <param name="preferredSpatialAggregationType">A single <see cref="AggregationTypes"/> indicating the default way
 92    /// <param name="missingSampleHandling">A <see cref="MissingSampleHandling"/> indicating how clients should treat mi
 93    /// <returns>An <see cref="IAmbientStatistic"/> the caller can use to update the statistic samples.</returns>
 94    IAmbientStatistic GetOrAddStatistic(AmbientStatisticType type, string id, string name, string description, bool repl
 95        , long initialValue = 0, long? minimumValue = null, long? maximumValue = null
 96        , string? units = null, double fixedFloatingPointAdjustment = 1.0
 97        , AggregationTypes temporalAggregationTypes = AggregationTypes.None
 98        , AggregationTypes spatialAggregationTypes = AggregationTypes.None
 99        , AggregationTypes preferredTemporalAggregationType = AggregationTypes.None
 100        , AggregationTypes preferredSpatialAggregationType = AggregationTypes.None
 101        , MissingSampleHandling missingSampleHandling = MissingSampleHandling.LinearEstimation
 102        );
 103    /// <summary>
 104    /// Removes the specified statistic if it exists.
 105    /// </summary>
 106    /// <returns>Whether or not the statistic was successfully removed.</returns>
 107    /// <remarks>
 108    /// Note that the ExecutionTime statistic cannot be removed and will return false from this function.
 109    /// </remarks>
 110    bool RemoveStatistic(string id);
 111    /// <summary>
 112    /// Adds or updates a ratio statistic with the specified identifier, description, and statistics.
 113    /// </summary>
 114    /// <param name="id">A dash-delimited identifier for the statistic.</param>
 115    /// <param name="name">A name for the statistic, presumably to use as a chart title.</param>
 116    /// <param name="description">A human-readable description for the statistic.</param>
 117    /// <param name="replaceIfAlreadyExists">true to use a new statistic even if one already exists, false to return an 
 118    /// <param name="units">An optional string describing the units of the statistic (after the decimal point is adjuste
 119    /// <param name="numeratorStatistic">The ID of the numerator statistic.  The constant 1 will be used if null.</param
 120    /// <param name="numeratorDelta">Whether or not the numerator should be the difference over time.</param>
 121    /// <param name="denominatorStatistic">The ID of the denominator statistic.  The constant 1 will be used if null.</p
 122    /// <param name="denominatorDelta">Whether or not the denominator should be the difference over time.</param>
 123    /// <returns>An <see cref="IAmbientRatioStatistic"/> the caller can use to access the ratio statistic data.</returns
 124    IAmbientRatioStatistic GetOrAddRatioStatistic(string id, string name, string description, bool replaceIfAlreadyExist
 125        , string? numeratorStatistic = null, bool numeratorDelta = true
 126        , string? denominatorStatistic = null, bool denominatorDelta = true
 127        );
 128    /// <summary>
 129    /// Removes the specified ratio statistic if it exists.
 130    /// </summary>
 131    /// <returns>Whether or not the ratio statistic was successfully removed.</returns>
 132    bool RemoveRatioStatistic(string id);
 133}
 134/// <summary>
 135/// An enumeration of the types of statistics that can be collected.
 136/// </summary>
 137public enum AmbientStatisticType
 138{
 139    /// <summary>
 140    /// A raw statistic is one that is not cumulative, that usually uses <see cref="IAmbientStatistic.SetRawValue"/> for
 141    /// </summary>
 142    Raw,
 143    /// <summary>
 144    /// A cumulative statistic is one that is added to over time, using <see cref="IAmbientStatistic.IncrementRaw"/> or 
 145    /// </summary>
 146    Cumulative,
 147    /// <summary>
 148    /// A min statistic is one that always uses <see cref="IAmbientStatistic.SetRawMin"/> for updates.
 149    /// </summary>
 150    Min,
 151    /// <summary>
 152    /// A min statistic is one that always uses <see cref="IAmbientStatistic.SetRawMax"/> for updates.
 153    /// </summary>
 154    Max,
 155}
 156/// <summary>
 157/// An enumeration of ways to aggregate statistic data.
 158/// </summary>
 159[Flags]
 160public enum AggregationTypes
 161{
 162    /// <summary>
 163    /// Since not aggregating doesn't ever make sense, we use this as the default value to do aggregation based on the <
 164    /// See remarks for details about temporal and spatial aggregation defaults for each type.
 165    /// </summary>
 166    /// <remarks>
 167    /// For <see cref="AmbientStatisticType.Raw"/>, temporal aggregation is <see cref="Average"/> and spatial aggregatio
 168    /// For <see cref="AmbientStatisticType.Cumulative"/>, temporal aggregation is <see cref="MostRecent"/> and spatial 
 169    /// For <see cref="AmbientStatisticType.Min"/>, temporal aggregation is <see cref="Min"/> and spatial aggregation is
 170    /// For <see cref="AmbientStatisticType.Max"/>, temporal aggregation is <see cref="Max"/> and spatial aggregation is
 171    /// </remarks>
 172    None = 0,
 173    /// <summary>
 174    /// The aggregation should sum the values.  This type of aggregation would be useful for statistics that count items
 175    /// Such statistics are not recommended unless the available graphing system can't display the change in time for a 
 176    /// </summary>
 177    Sum = 1,
 178    /// <summary>
 179    /// The aggregation should average the values.  Statistics that use <see cref="IAmbientStatistic.SetRawValue"/> migh
 180    /// </summary>
 181    Average = 2,
 182    /// <summary>
 183    /// The aggregation should take the least of the values.  Statistics that use <see cref="IAmbientStatistic.SetRawMin
 184    /// </summary>
 185    Min = 4,
 186    /// <summary>
 187    /// The aggregation should take the greatest of the values.  Statistics that use <see cref="IAmbientStatistic.SetRaw
 188    /// </summary>
 189    Max = 8,
 190    /// <summary>
 191    /// The aggregation should take the most recent value.  Statistics that use <see cref="IAmbientStatistic.SetRawValue
 192    /// For spatial aggregation, this would only be useful if every system is reporting some value from a shared externa
 193    /// </summary>
 194    MostRecent = 16,
 195}
 196/// <summary>
 197/// An enumeration indicating how missing (null) samples should be handled, usually on the client side (perhaps on the s
 198/// </summary>
 199public enum MissingSampleHandling
 200{
 201    /// <summary>
 202    /// When samples get missed, the missing samples should just be ignored.  This is useful when you want to see missin
 203    /// </summary>
 204    Skip,
 205    /// <summary>
 206    /// When samples get missed, the missing samples should be filled in with zeros.
 207    /// </summary>
 208    Zero,
 209    /// <summary>
 210    /// When samples get missed, the missing values should be filled in using linear estimation.  This is the default ty
 211    /// </summary>
 212    LinearEstimation,
 213    /// <summary>
 214    /// When samples get missed, the missing values should be filled in using exponential estimation.
 215    /// </summary>
 216    ExponentialEstimation,
 217    /// <summary>
 218    /// When samples get missed, the missing values should be filled in using logarithmic estimation.
 219    /// </summary>
 220    LogarithmicEstimation,
 221}
 222/// <summary>
 223/// An interface that gives read access to a single statistic.
 224/// Note that many user-facing statistics will naturally be a ratio of the samples of two statistics or the changes in t
 225/// </summary>
 226/// <remarks>
 227/// <pitch>The read side of one statistic: the current raw sample plus the immutable metadata a consumer needs to displa
 228/// <pledge>
 229/// Everything except <see cref="CurrentRawValue"/> is immutable for the life of the statistic.  <see cref="CurrentRawVa
 230/// The aggregation-type and missing-sample-handling properties are advice to consumers, not behavior of this object — t
 231/// </pledge>
 232/// </remarks>
 233public interface IAmbientStatisticReader
 234{
 235    /// <summary>
 236    /// Gets the <see cref="IAmbientStatistics"/> this statistic belongs to.
 237    /// </summary>
 238    IAmbientStatistics StatisticsSet { get; }
 239    /// <summary>
 240    /// Gets the <see cref="AmbientStatisticType"/> for the statistic.  Immutable.
 241    /// </summary>
 242    AmbientStatisticType StatisticType { get; }
 243    /// <summary>
 244    /// Gets the identifier for the statistic.
 245    /// The identifier should be a dash-delimited path identifying the data.  Immutable.
 246    /// </summary>
 247    string Id { get; }
 248    /// <summary>
 249    /// Gets a human-readable name, presumably for the chart title.  Should describe the adjusted values, not the raw va
 250    /// </summary>
 251    string Name { get; }
 252    /// <summary>
 253    /// Gets a human-readable description of this statistic.  Should describe the adjusted values, not the raw values.  
 254    /// </summary>
 255    string Description { get; }
 256    /// <summary>
 257    /// Gets the current statistic sample value.  Thread-safe, possibly interlocked.
 258    /// </summary>
 259    long CurrentRawValue { get; }
 260    /// <summary>
 261    /// The expected minimum value, if any.  Null if there is no expected minimum.  Immutable.
 262    /// </summary>
 263    long? ExpectedMinimumRawValue { get; }
 264    /// <summary>
 265    /// The expected maximum value, if any.  Null if there is no expected maximum.  Immutable.
 266    /// </summary>
 267    long? ExpectedMaximumRawValue { get; }
 268    /// <summary>
 269    /// Gets an optional human-readable units name, presumably for the y-axis of the chart.
 270    /// Assumes that the numbers in the axis have already been divided by <see cref="FixedFloatingPointAdjustment"/>.
 271    /// Immutable.
 272    /// </summary>
 273    string? AdjustedUnits { get; }
 274    /// <summary>
 275    /// The number used to divide <see cref="CurrentRawValue"/> to adjust the integer sample into a floating point numbe
 276    /// Immutable.
 277    /// </summary>
 278    double FixedFloatingPointAdjustment { get; }
 279    /// <summary>
 280    /// The types of aggregation that should be used when aggregating samples over time.
 281    /// Immutable.
 282    /// </summary>
 283    AggregationTypes TemporalAggregationTypes { get; }
 284    /// <summary>
 285    /// The types of aggregation that should be used when aggregating samples from different systems.
 286    /// Immutable.
 287    /// </summary>
 288    AggregationTypes SpatialAggregationTypes { get; }
 289    /// <summary>
 290    /// The type of aggregation that should be used when aggregating samples over time and only one aggregation can be k
 291    /// Immutable.
 292    /// </summary>
 293    AggregationTypes PreferredTemporalAggregationType { get; }
 294    /// <summary>
 295    /// The type of aggregation that should be used when aggregating samples from different systems and only one aggrega
 296    /// Immutable.
 297    /// </summary>
 298    AggregationTypes PreferredSpatialAggregationType { get; }
 299    /// <summary>
 300    /// How missing samples should be handled.
 301    /// Immutable.
 302    /// </summary>
 303    MissingSampleHandling MissingSampleHandling { get; }
 304}
 305/// <summary>
 306/// An interface that gives write access to a single statistic.
 307/// Implementations are disposable, but should not throw exceptions if methods are called after disposal.
 308/// Disposability is meant to stop reporting results.
 309/// </summary>
 310/// <remarks>
 311/// <pitch>The write side of one statistic: atomic raw-sample updates cheap enough to call on every operation, in whiche
 312/// <pledge><see cref="IAmbientStatisticReader"/></pledge>
 313/// <pledge>
 314/// All updates are atomic and thread-safe, and concurrent updates never lose each other's effects (min/max updates only
 315/// Disposal deregisters the statistic from its <see cref="IAmbientStatistics"/> to stop reporting; calling any member a
 316/// </pledge>
 317/// </remarks>
 318public interface IAmbientStatistic : IAmbientStatisticReader, IDisposable
 319{
 320    /// <summary>
 321    /// Increments the raw statistic sample value.  This value is not adjusted by <see cref="IAmbientStatisticReader.Fix
 322    /// </summary>
 323    /// <returns>The incremented sample value.</returns>
 324    long IncrementRaw();
 325    /// <summary>
 326    /// Decrements the raw statistic sample value.  This value is not adjusted by <see cref="IAmbientStatisticReader.Fix
 327    /// </summary>
 328    /// <returns>The decremented sample value.</returns>
 329    long DecrementRaw();
 330    /// <summary>
 331    /// Adds to the raw statistic sample value.  This value is not adjusted by <see cref="IAmbientStatisticReader.FixedF
 332    /// </summary>
 333    /// <param name="addend">The amount to add to the statistic sample value.</param>
 334    /// <returns>The new sample value.</returns>
 335    long AddRaw(long addend);
 336    /// <summary>
 337    /// Sets the raw integer statistic sample value.  This value is not adjusted by <see cref="IAmbientStatisticReader.F
 338    /// </summary>
 339    /// <param name="newValue">The new value to use.</param>
 340    void SetRawValue(long newValue);
 341    /// <summary>
 342    /// Sets the raw statistic sample minimum value.  This value is not adjusted by <see cref="IAmbientStatisticReader.F
 343    /// </summary>
 344    /// <param name="newPossibleMinValue">A value which will be the new sample value if it is smaller than the current s
 345    /// <returns>The new sample value.</returns>
 346    long SetRawMin(long newPossibleMinValue);
 347    /// <summary>
 348    /// Sets the raw statistic sample maximum value.  This value is not adjusted by <see cref="IAmbientStatisticReader.F
 349    /// </summary>
 350    /// <param name="newPossibleMaxValue">A value which will be the new sample value if it is larger than the current sa
 351    /// <returns>The new sample value.</returns>
 352    long SetRawMax(long newPossibleMaxValue);
 353}
 354/// <summary>
 355/// An interface that indicates that a useful statistic exists that is the ratio of two other statistics or the change o
 356/// These statistics should not be recorded independently, but should be recorded as a ratio of the two specified statis
 357/// Using ratio statistics instead of computing the ratio on the server prevents the need to send the raw data to the cl
 358/// For example, if a statistic for the average request processing time is computed on the server, aggregating that rati
 359/// Using ratio statistics to compute the ratios on the aggregated raw data weights all the requests equally, whereas us
 360/// Implementations are disposable, but should not throw exceptions if methods are called after disposal.
 361/// Disposability is meant to stop reporting results.
 362/// </summary>
 363/// <remarks>
 364/// <pitch>A declaration that a useful user-facing value is the ratio of two statistics (or of their changes over time) 
 365/// <pledge>
 366/// A pure descriptor: it carries no samples and performs no computation — consumers resolve the numerator and denominat
 367/// All properties are immutable; disposal deregisters the descriptor and must not throw afterwards.
 368/// </pledge>
 369/// </remarks>
 370public interface IAmbientRatioStatistic : IDisposable
 371{
 372    /// <summary>
 373    /// Gets the <see cref="IAmbientStatistics"/> this statistic belongs to.
 374    /// </summary>
 375    IAmbientStatistics StatisticsSet { get; }
 376    /// <summary>
 377    /// Gets the identifier for the statistic.
 378    /// The identifier should be a dash-delimited path identifying the data.  Immutable.
 379    /// </summary>
 380    string Id { get; }
 381    /// <summary>
 382    /// Gets a human-readable name, presumably for the chart title.  Should describe the adjusted values, not the raw va
 383    /// </summary>
 384    string Name { get; }
 385    /// <summary>
 386    /// Gets a human-readable description of this statistic.  Should describe the adjusted values, not the raw values.  
 387    /// </summary>
 388    string Description { get; }
 389    /// <summary>
 390    /// Gets an optional human-readable units name, presumably for the y-axis of the chart.
 391    /// Immutable.
 392    /// </summary>
 393    string? AdjustedUnits { get; }
 394    /// <summary>
 395    /// Gets the ID of the numerator statistic.  Use the constant 1 (and ignore <see cref="NumeratorDelta"/>) if null.  
 396    /// </summary>
 397    string? NumeratorStatisticId { get; }
 398    /// <summary>
 399    /// The numerator should be the change in the numerator statistic over time rather than the raw value.  Immutable.
 400    /// </summary>
 401    bool NumeratorDelta { get; }
 402    /// <summary>
 403    /// Gets the ID of the denominator statistic, often the built-in "ExecutionTime" statistic.  Use 1 (and ignore <see 
 404    /// </summary>
 405    string? DenominatorStatisticId { get; }
 406    /// <summary>
 407    /// The denominator should be the change in the numerator statistic over time rather than the raw value.  Immutable.
 408    /// </summary>
 409    bool DenominatorDelta { get; }
 410}
 411
 412/// <summary>
 413/// A class that contains extension methods for various statistics interfaces that add functions to aggregate statistic 
 414/// </summary>
 415/// <remarks>
 416/// <pitch>The client-side math for statistics: set adjusted (display-unit) values without hand-multiplying by the fixed
 417/// <pledge>
 418/// Adjusted-value setters convert display units to raw samples using the statistic's own adjustment factor and are as t
 419/// Aggregation treats a <see cref="AggregationTypes.None"/> preference as "use the default for the statistic's type"; S
 420/// </pledge>
 421/// </remarks>
 422public static class IAmbientStatisticsExtensions
 423{
 424    /// <summary>
 425    /// Sets the statistic sample value in the units specified by <see cref="IAmbientStatisticReader.AdjustedUnits"/>, a
 426    /// </summary>
 427    /// <param name="statistic">The <see cref="IAmbientStatistic"/> whose value should be set.</param>
 428    /// <param name="newValue">The new value to use.</param>
 429    public static void SetValue(this IAmbientStatistic statistic, long newValue)
 430    {
 2431        if (statistic == null) throw new ArgumentNullException(nameof(statistic));
 2432        statistic.SetRawValue((long)(newValue * statistic.FixedFloatingPointAdjustment));
 2433    }
 434    /// <summary>
 435    /// Sets the statistic sample value in the units specified by <see cref="IAmbientStatisticReader.AdjustedUnits"/>, a
 436    /// </summary>
 437    /// <param name="statistic">The <see cref="IAmbientStatistic"/> whose value should be set.</param>
 438    /// <param name="newValue">The new value to use.</param>
 439    public static void SetValue(this IAmbientStatistic statistic, float newValue)
 440    {
 2441        if (statistic == null) throw new ArgumentNullException(nameof(statistic));
 2442        statistic.SetRawValue((long)(newValue * statistic.FixedFloatingPointAdjustment));
 2443    }
 444    /// <summary>
 445    /// Sets the statistic sample value in the units specified by <see cref="IAmbientStatisticReader.AdjustedUnits"/>, a
 446    /// </summary>
 447    /// <param name="statistic">The <see cref="IAmbientStatistic"/> whose value should be set.</param>
 448    /// <param name="newValue">The new value to use.</param>
 449    public static void SetValue(this IAmbientStatistic statistic, double newValue)
 450    {
 2451        if (statistic == null) throw new ArgumentNullException(nameof(statistic));
 2452        statistic.SetRawValue((long)(newValue * statistic.FixedFloatingPointAdjustment));
 2453    }
 454    /// <summary>
 455    /// Uses the preferred aggregation type to aggregate samples from a time range.
 456    /// </summary>
 457    /// <param name="reader">The reader to use to aggregate the samples.</param>
 458    /// <param name="samples">An enumeration of samples to aggregate.</param>
 459    /// <returns>The preferred temporally aggregated sample.</returns>
 460    public static long? PreferredTemporalAggregation(this IAmbientStatisticReader reader, IEnumerable<long?> samples)
 461    {
 2462        if (reader == null) throw new ArgumentNullException(nameof(reader));
 2463        AggregationTypes types = reader.PreferredTemporalAggregationType;
 2464        if (types == AggregationTypes.None) types = DefaultTemporalAggregation(reader.StatisticType);
 2465        return types.Aggregate(samples);
 466    }
 467    /// <summary>
 468    /// Uses the preferred aggregation type to aggregate samples from different systems.
 469    /// </summary>
 470    /// <param name="reader">The reader to use to aggregate the samples.</param>
 471    /// <param name="samples">An enumeration of samples to aggregate.</param>
 472    /// <returns>The preferred temporally aggregated sample.</returns>
 473    public static long? PreferredSpatialAggregation(this IAmbientStatisticReader reader, IEnumerable<long?> samples)
 474    {
 2475        if (reader == null) throw new ArgumentNullException(nameof(reader));
 2476        AggregationTypes types = reader.PreferredSpatialAggregationType;
 2477        if (types == AggregationTypes.None) types = DefaultSpatialAggregation(reader.StatisticType);
 2478        return types.Aggregate(samples);
 479    }
 480    /// <summary>
 481    /// Uses the specified aggregation type to aggregate samples.
 482    /// </summary>
 483    /// <param name="type">The <see cref="AggregationTypes"/> to use to aggregate the samples (only one may be set).</pa
 484    /// <param name="samples">An enumeration of samples to aggregate.</param>
 485    /// <returns>The aggregated sample.</returns>
 486    public static long? Aggregate(this AggregationTypes type, IEnumerable<long?> samples)
 487    {
 2488        if (samples == null) throw new ArgumentNullException(nameof(samples));
 2489        return type switch
 2490        {
 2491            AggregationTypes.Average => Average(samples),
 2492            AggregationTypes.Min => samples.Min(),
 2493            AggregationTypes.Max => samples.Max(),
 2494            AggregationTypes.MostRecent => samples.LastOrDefault(),
 2495            _ => SumWithCutoff(samples),
 2496        };
 497    }
 498    private static long? Average(IEnumerable<long?> samples)
 499    {
 2500        double? avg = samples.Select(l => (double?)l).Average();
 2501        return (avg == null) ? null : (long?)Math.Round(avg.Value);
 502    }
 503    private static long? SumWithCutoff(IEnumerable<long?> samples)
 504    {
 2505        double? sum = null;
 2506        foreach (long? l in samples)
 507        {
 2508            if (l != null) sum = (sum ?? 0) + l.Value;
 509        }
 2510        if (sum > long.MaxValue) return long.MaxValue;
 2511        return (long?)sum;
 512    }
 513    /// <summary>
 514    /// Gets the default temporal (over time) aggregation type for the specified statistic type.
 515    /// </summary>
 516    /// <param name="statisicType">The <see cref="AmbientStatisticType"/> for the statistic.</param>
 517    /// <returns>The default <see cref="AggregationTypes"/> for aggregating samples for specified statistic over time.</
 518    public static AggregationTypes DefaultTemporalAggregation(this AmbientStatisticType statisicType)
 519    {
 2520        return statisicType switch
 2521        {
 2522            AmbientStatisticType.Raw => AggregationTypes.Average,
 2523            AmbientStatisticType.Cumulative => AggregationTypes.MostRecent,
 2524            AmbientStatisticType.Min => AggregationTypes.Min,
 2525            AmbientStatisticType.Max => AggregationTypes.Max,
 2526            _ => AggregationTypes.Average,
 2527        };
 528    }
 529    /// <summary>
 530    /// Gets the default spatial (cross-system) aggregation type for the specified statistic type.
 531    /// </summary>
 532    /// <param name="statisicType">The <see cref="AmbientStatisticType"/> for the statistic.</param>
 533    /// <returns>The default <see cref="AggregationTypes"/> for aggregating samples for specified statistic across syste
 534    public static AggregationTypes DefaultSpatialAggregation(this AmbientStatisticType statisicType)
 535    {
 2536        return statisicType switch
 2537        {
 2538            AmbientStatisticType.Raw => AggregationTypes.Average,
 2539            AmbientStatisticType.Cumulative => AggregationTypes.Average,
 2540            AmbientStatisticType.Min => AggregationTypes.Min,
 2541            AmbientStatisticType.Max => AggregationTypes.Max,
 2542            _ => AggregationTypes.Average,
 2543        };
 544    }
 545}