/// <summary>
        /// Flushes the values buffer to update the aggregate state held by subclasses.
        /// </summary>
        /// <param name="buffer"></param>
        private void UpdateAggregate(MetricValuesBufferBase <TBufferedValue> buffer)
        {
            if (buffer == null)
            {
                return;
            }

#if DEBUG
            unchecked
            {
                Interlocked.Increment(ref s_countBufferFlushes);
            }
#endif

            object stage1Result;

            // This lock is only contended is a user called CreateAggregateUnsafe or CompleteAggregation.
            // This is very unlikely to be the case in a tight loop.
            lock (buffer)
            {
                int maxFlushIndex = Math.Min(buffer.PeekLastWriteIndex(), buffer.Capacity - 1);
                int minFlushIndex = buffer.NextFlushIndex;

                if (minFlushIndex > maxFlushIndex)
                {
                    return;
                }

                stage1Result = UpdateAggregate_Stage1(buffer, minFlushIndex, maxFlushIndex);

                buffer.NextFlushIndex = maxFlushIndex + 1;
            }

            UpdateAggregate_Stage2(stage1Result);
        }
        private MetricValuesBufferBase <TBufferedValue> InvokeMetricValuesBufferFactory()
        {
#if DEBUG
            unchecked
            {
                Interlocked.Increment(ref s_countNewBufferObjectsCreated);
            }
#endif
            MetricValuesBufferBase <TBufferedValue> buffer = _metricValuesBufferFactory();

            if (buffer == null)
            {
                throw new InvalidOperationException($"{nameof(_metricValuesBufferFactory)}-delegate returned null. This is not allowed. Bad aggregator?");
            }

            return(buffer);
        }
        /// <summary />
        /// <param name="metricValuesBufferFactory"></param>
        /// <param name="configuration"></param>
        /// <param name="dataSeries"></param>
        /// <param name="aggregationCycleKind"></param>
        protected MetricSeriesAggregatorBase(
            Func <MetricValuesBufferBase <TBufferedValue> > metricValuesBufferFactory,
            IMetricSeriesConfiguration configuration,
            MetricSeries dataSeries,
            MetricAggregationCycleKind aggregationCycleKind)
        {
            Util.ValidateNotNull(metricValuesBufferFactory, nameof(metricValuesBufferFactory));
            Util.ValidateNotNull(configuration, nameof(configuration));

            _dataSeries           = dataSeries;
            _aggregationCycleKind = aggregationCycleKind;
            _isPersistent         = configuration.RequiresPersistentAggregation;

            _metricValuesBufferFactory = metricValuesBufferFactory;
            _metricValuesBuffer        = InvokeMetricValuesBufferFactory();

            Reset(default(DateTimeOffset), default(IMetricValueFilter));
        }
        /// <summary>
        /// This method is the meat of the lock-free aggregation logic.
        /// </summary>
        /// <param name="metricValue">Already filtered and conveted value to be tracked.
        ///     We know that the value is not Double.NaN and not null and it passed trought any filters.</param>
        private void TrackFilteredConvertedValue(TBufferedValue metricValue)
        {
            // Get reference to the current buffer:
            MetricValuesBufferBase <TBufferedValue> buffer = _metricValuesBuffer;

            // Get the index at which to store metricValue into the buffer:
            int index = buffer.IncWriteIndex();

            // Check to see whether we are past the end of the buffer.
            // If we are, it means that some *other* thread hit exactly the end (wrote the last value that fits into the buffer) and is currently flushing.
            // If we are, we will spin and wait.
            if (index >= buffer.Capacity)
            {
#if DEBUG
                int startMillis = Environment.TickCount;
#endif
#pragma warning disable SA1129 // Do not use default value type constructor
                var spinWait = new SpinWait();
#pragma warning restore SA1129 // Do not use default value type constructor

                // It could be that the thread that was flushing is done and has updated the buffer pointer.
                // We refresh our local reference and see if we now have a valid index into the buffer.
                buffer = _metricValuesBuffer;
                index  = buffer.IncWriteIndex();

                while (index >= buffer.Capacity)
                {
                    // Still not valid index into the buffer. Spin and try again.
                    spinWait.SpinOnce();
#if DEBUG
                    unchecked
                    {
                        Interlocked.Increment(ref s_countBufferWaitSpinCycles);
                    }
#endif
                    if (spinWait.Count % 100 == 0)
                    {
                        // In tests (including stress tests) we always finished wating before 100 cycles.
                        // However, this is a protection against en extreme case on a slow machine.
                        // We will back off and sleep for a few millisecs to give the machine a chance to finish current tasks.

                        Task.Delay(10).ConfigureAwait(continueOnCapturedContext: false).GetAwaiter().GetResult();
                    }

                    // Check to see whether the thread that was flushing is done and has updated the buffer pointer.
                    // We refresh our local reference and see if we now have a valid index into the buffer.
                    buffer = _metricValuesBuffer;
                    index  = buffer.IncWriteIndex();
                }
#if DEBUG
                unchecked
                {
                    int periodMillis      = Environment.TickCount - startMillis;
                    int currentSpinMillis = s_timeBufferWaitSpinMillis;
                    int prevSpinMillis    = Interlocked.CompareExchange(ref s_timeBufferWaitSpinMillis, currentSpinMillis + periodMillis, currentSpinMillis);
                    while (prevSpinMillis != currentSpinMillis)
                    {
                        currentSpinMillis = s_timeBufferWaitSpinMillis;
                        prevSpinMillis    = Interlocked.CompareExchange(ref s_timeBufferWaitSpinMillis, currentSpinMillis + periodMillis, currentSpinMillis);
                    }

                    Interlocked.Increment(ref s_countBufferWaitSpinEvents);
                }
#endif
            }

            // Ok, so now we know that (0 <= index = buffer.Capacity). Write the value to the buffer:
            buffer.WriteValue(index, metricValue);

            // If this was the last value that fits into the buffer, we must flush the buffer:
            if (index == buffer.Capacity - 1)
            {
                // Before we begin flushing (which is can take time), we update the _metricValuesBuffer to a fresh buffer that is ready to take values.
                // That way threads do notneed to spin and wait until we flush and can begin writing values.

                // We try to recycle a previous buffer to lower stress on GC and to lower Gen-2 heap fragmentation.
                // The lifetime of an buffer can easily be a minute or so and then it can get into Gen-2 GC heap.
                // If we then, keep throwing such buffers away we can fragment the Gen-2 heap. To avoid this we employ
                // a simple form of best-effort object pooling.

                // Get buffer from pool and reset the pool:
                MetricValuesBufferBase <TBufferedValue> newBufer = Interlocked.Exchange(ref _metricValuesBufferRecycle, null);

                if (newBufer != null)
                {
                    // If we were succesful in getting a recycled buffer from the pool, we will try to use it as the new buffer.
                    // If we successfully the the recycled buffer to be the new buffer, we will reset it to prepare for data.
                    // Otherwise we will just throw it away.

                    MetricValuesBufferBase <TBufferedValue> prevBuffer = Interlocked.CompareExchange(ref _metricValuesBuffer, newBufer, buffer);
                    if (prevBuffer == buffer)
                    {
                        newBufer.ResetIndices();
                    }
                }
                else
                {
                    // If we were succesful in getting a recycled buffer from the pool, we will create a new one.

                    newBufer = InvokeMetricValuesBufferFactory();
                    Interlocked.CompareExchange(ref _metricValuesBuffer, newBufer, buffer);
                }

                // Ok, now we have either set a new buffer that is ready to be used, or we have determined using CompareExchange
                // that another thread set a new buffer and we do not need to do it here.

                // Now we can actually flush the buffer:

                UpdateAggregate(buffer);

                // The buffer is now flushed. If the slot for the best-effor object pooling is free, use it:
                Interlocked.CompareExchange(ref _metricValuesBufferRecycle, buffer, null);
            }
        }
 /// <summary>
 /// </summary>
 /// <param name="buffer"></param>
 /// <param name="minFlushIndex"></param>
 /// <param name="maxFlushIndex"></param>
 /// <returns></returns>
 protected abstract object UpdateAggregate_Stage1(MetricValuesBufferBase <TBufferedValue> buffer, int minFlushIndex, int maxFlushIndex);