#region Copyright 2013-2014 by Roger Knapp, Licensed under the Apache License, Version 2.0
/* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#endregion
using System;
using System.Threading;
namespace CSharpTest.Net.Threading
{
///
/// A low-precision, but fast, counter that estimates how often the Increment() method is being
/// called on this instance. The precision is roughly +/- 1.7% for every 1000 calls/sec;
/// so at 20000 calls/second expect the Value property to be +/- 34%. If you looking for
/// something cheap to use to 'push-back' on overbearing clients this should suite most needs.
/// If more accuracy is desired at higher volumes, specify the sampleFreq to scale the data
/// samples under 1000/sec.
///
///
/// Yes you can build this with a Stopwatch.GetTimestamp() instead of DateTime.UtcNow; hoewever
/// the Stopwatch API is 100x slower than using DateTime.UtcNow and although more precise, it
/// generally does not provide significant gains in the sub 50k call range. Generally you can
/// just increase sampleFreq to make up for the lack of precision in DateTime.UtcNow.
///
public class CallsPerSecondCounter
{
private long _writeIx;
private readonly int _sampleFreq;
private readonly int _sampleSize;
private readonly long[] _samples;
private readonly long _sampleScale;
///
/// Creates a CallsPerSecondCounter with a specified sample size. A reasonable sample size
/// is 100, although even as low as 25 will often yeild reasonable results. The higher the
/// sample size, the longer it takes for Value to drop to zero, for a sample size of 100 the
/// counter value will reach zero aprox 50 seconds after the last activity.
///
public CallsPerSecondCounter([System.ComponentModel.DefaultValue(50)] int sampleSize) : this(sampleSize, 1)
{ }
///
/// Creates a CallsPerSecondCounter with a specified sample size and freqency of collection.
/// A reasonable sample size is 100, although even as low as 25 will often yeild reasonable
/// results. The higher the sample size, the longer it takes for Value to drop to zero, for
/// a sample size of 100 the counter value will reach zero aprox 50 seconds after the last
/// activity. The sampleFreq defines how often a sample is taken, for instance a value of 5
/// means to collect a sample only every 5th call to Increment() whereas a value of 1 will
/// collect on every call. For high sampling rates this value can be increased to improve
/// accuracy; however, it will also multiply the warm-up and cool-down time required for the
/// Value property to move off of zero or move to zero.
///
public CallsPerSecondCounter([System.ComponentModel.DefaultValue(50)] int sampleSize, int sampleFreq)
{
if (sampleSize <= 0) throw new ArgumentOutOfRangeException("sampleSize");
if (sampleFreq <= 0) throw new ArgumentOutOfRangeException("sampleFreq");
_sampleSize = sampleSize;
_sampleFreq = sampleFreq;
_sampleScale = TimeSpan.TicksPerSecond*_sampleFreq*_sampleSize/2;
_writeIx = 0;
_samples = new long[_sampleSize];
var now = DateTime.UtcNow.AddDays(-1).Ticks;
for (int i = 0; i < _sampleSize; i++)
_samples[i] = now;
}
///
/// Returns an estimated number of times per second the Increment() call is being used.
///
public long Value
{
get
{
double avg = 0;
long before = DateTime.UtcNow.Ticks;
for (int i = 0; i < _sampleSize; i++)
avg += Interlocked.CompareExchange(ref _samples[i], 0, 0);
long now = (DateTime.UtcNow.Ticks + before) / 2;
avg = avg / _sampleSize;
double delta = now - avg;
if (delta <= 0) return TimeSpan.TicksPerSecond;
double result = _sampleScale / delta;
return (long)(result + 0.5);
}
}
///
/// Called to increment the usage by 1.
///
public void Increment()
{
var ix = Interlocked.Increment(ref _writeIx);
if (ix % _sampleFreq == 0)
{
Interlocked.Exchange(
ref _samples[(ix/_sampleFreq)%_sampleSize],
DateTime.UtcNow.Ticks);
}
}
}
}