protected override object UpdateAggregate_Stage1(MetricValuesBufferBase <double> buffer, int minFlushIndex, int maxFlushIndex) { Data bufferData = new Data(); bufferData.Count = 0; bufferData.Min = Double.MaxValue; bufferData.Max = Double.MinValue; bufferData.Sum = 0.0; bufferData.SumOfSquares = 0.0; for (int index = minFlushIndex; index <= maxFlushIndex; index++) { double metricValue = buffer.GetAndResetValue(index); if (Double.IsNaN(metricValue)) { continue; } bufferData.Count++; bufferData.Max = (metricValue > bufferData.Max) ? metricValue : bufferData.Max; bufferData.Min = (metricValue < bufferData.Min) ? metricValue : bufferData.Min; bufferData.Sum += metricValue; bufferData.SumOfSquares += metricValue * metricValue; } if (this.restrictToUInt32Values) { bufferData.Max = Math.Round(bufferData.Max); bufferData.Min = Math.Round(bufferData.Min); } return(bufferData); }
protected override object UpdateAggregate_Stage1(MetricValuesBufferBase <double> buffer, int minFlushIndex, int maxFlushIndex) { // Compute a summary of the buffer: Data bufferData = new Data(); bufferData.HasValues = false; bufferData.Min = Double.MaxValue; bufferData.Max = Double.MinValue; bufferData.Last = Double.NaN; for (int index = minFlushIndex; index <= maxFlushIndex; index++) { double metricValue = buffer.GetAndResetValue(index); if (Double.IsNaN(metricValue)) { continue; } bufferData.HasValues = true; bufferData.Max = (metricValue > bufferData.Max) ? metricValue : bufferData.Max; bufferData.Min = (metricValue < bufferData.Min) ? metricValue : bufferData.Min; bufferData.Last = metricValue; } return(bufferData); }
/// <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); }
protected override object UpdateAggregate_Stage1(MetricValuesBufferBase <object> buffer, int minFlushIndex, int maxFlushIndex) { lock (_updateLock) { for (int index = minFlushIndex; index <= maxFlushIndex; index++) { object metricValue = buffer.GetAndResetValue(index); if (metricValue == null) { continue; } string stringValue = metricValue.ToString(); if (!_caseSensitive) { stringValue = stringValue.ToLowerInvariant(); } _uniqueValues.Add(stringValue); Interlocked.Increment(ref _totalValuesCount); } } return(null); }
protected override object UpdateAggregate_Stage1(MetricValuesBufferBase <double> buffer, int minFlushIndex, int maxFlushIndex) { lock (_updateLock) { for (int index = minFlushIndex; index <= maxFlushIndex; index++) { double metricValue = buffer.GetAndResetValue(index); if (Double.IsNaN(metricValue)) { continue; } _sum += metricValue; _max = (_sum > _max) ? _sum : _max; _min = (_sum < _min) ? _sum : _min; } if (_restrictToUInt32Values) { _sum = Math.Round(_sum); _max = Math.Round(_max); _min = Math.Round(_min); } } return(null); }
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); }
public 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 var spinWait = new SpinWait(); // 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 // 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 th emachine // a chance to finisah current tasks. if (spinWait.Count % 100 == 0) { 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); } }
protected abstract object UpdateAggregate_Stage1(MetricValuesBufferBase <TBufferedValue> buffer, int minFlushIndex, int maxFlushIndex);