File: System\Diagnostics\Metrics\CounterAggregator.cs
Web Access
Project: src\src\libraries\System.Diagnostics.DiagnosticSource\src\System.Diagnostics.DiagnosticSource.csproj (System.Diagnostics.DiagnosticSource)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Runtime.InteropServices;
using System.Threading;
using Internal;
 
namespace System.Diagnostics.Metrics
{
    internal sealed class CounterAggregator : Aggregator
    {
        private readonly bool _isMonotonic;
        private double _aggregatedValue;
        /// <summary>Per-core deltas.</summary>
        /// <remarks>
        /// Stored as an array of deltas rather than a single delta to reduce contention from
        /// highly-parallel updates. The elements are padded to reduce false sharing.
        /// The array is limited to a semi-arbitrary limit of 8 in order to avoid excessive memory
        /// consumption when many counters are being used.
        /// </remarks>
        private readonly PaddedDouble[] _deltas = new PaddedDouble[Math.Min(Environment.ProcessorCount, 8)];
 
        public CounterAggregator(bool isMonotonic)
        {
            _isMonotonic = isMonotonic;
        }
 
        public override void Update(double value)
        {
            // Get the deltas array.
            PaddedDouble[] deltas = _deltas;
 
            // Get the delta best associated with the current thread, preferring to use core ID rather than
            // thread ID to reduce contention.
            ref PaddedDouble delta = ref deltas[
#if NET
                Thread.GetCurrentProcessorId()
#else
                Environment.CurrentManagedThreadId
#endif
                % deltas.Length];
 
            // We're not guaranteed uncontented access, so we still need to add the value
            // to the delta with synchronization. Contention could come from other threads
            // assigned to the same slot or from Collect zero'ing out the delta.
            double currentValue;
            do
            {
                currentValue = delta.Value;
            }
            while (Interlocked.CompareExchange(ref delta.Value, currentValue + value, currentValue) != currentValue);
        }
 
        public override IAggregationStatistics Collect()
        {
            double delta, aggregatedValue;
            lock (this)
            {
                // Sum the deltas, resetting them to zero as we go. These resets needs to synchronize
                // with the additions performed in Update.
                delta = 0;
                foreach (ref PaddedDouble paddedDelta in _deltas.AsSpan())
                {
                    delta += Interlocked.Exchange(ref paddedDelta.Value, 0);
                }
 
                // Add the delta to the aggregated value.
                _aggregatedValue += delta;
                aggregatedValue = _aggregatedValue;
            }
 
            return new CounterStatistics(delta, _isMonotonic, aggregatedValue);
        }
 
        // 64 bytes is the size of a cache line on many systems. We pad the double to false sharing.
        // For the rare systems with a larger cache line, we may simply incur a little more false
        // sharing. This is a trade-off between throughput and memory footprint.
        [StructLayout(LayoutKind.Explicit, Size = 64)]
        private struct PaddedDouble
        {
            [FieldOffset(0)]
            public double Value;
        }
    }
 
    internal sealed class ObservableCounterAggregator : Aggregator
    {
        private readonly bool _isMonotonic;
        private double? _prevValue;
        private double _currValue;
 
        public ObservableCounterAggregator(bool isMonotonic)
        {
            _isMonotonic = isMonotonic;
        }
 
        public override void Update(double value)
        {
            Volatile.Write(ref _currValue, value);
        }
 
        public override IAggregationStatistics Collect()
        {
            double? delta = null;
            double currentValue;
            lock (this)
            {
                currentValue = Volatile.Read(ref _currValue);
 
                if (_prevValue.HasValue)
                {
                    delta = currentValue - _prevValue.Value;
                }
 
                _prevValue = currentValue;
            }
 
            return new CounterStatistics(delta, _isMonotonic, currentValue);
        }
    }
 
    internal sealed class CounterStatistics : IAggregationStatistics
    {
        public CounterStatistics(double? delta, bool isMonotonic, double value)
        {
            Delta = delta;
            IsMonotonic = isMonotonic;
            Value = value;
        }
 
        public double? Delta { get; }
 
        public bool IsMonotonic { get; }
 
        public double Value { get; }
    }
}