{
// In order to handle hung requests, we also capture metrics on a regular interval.
// This acts as a wake up timer, since we cannot rely on Activity1Stop.
- private readonly bool _supportHeartbeat;
+ // Note this value is parameterizable due to limitations in event counters: only 1 interval duration is
+ // respected. This gives the trigger infrastructure a way to use the same interval.
+ private readonly float? _heartbeatIntervalSeconds;
- public const int DefaultHeartbeatInterval = 10;
-
- public AspNetTriggerSourceConfiguration(bool supportHeartbeat = false)
+ public AspNetTriggerSourceConfiguration(float? heartbeatIntervalSeconds = null)
{
- _supportHeartbeat = supportHeartbeat;
+ _heartbeatIntervalSeconds = heartbeatIntervalSeconds;
}
/// <summary>
public override IList<EventPipeProvider> GetProviders()
{
- if (_supportHeartbeat)
+ if (_heartbeatIntervalSeconds.HasValue)
{
return new AggregateSourceConfiguration(
- new AspNetTriggerSourceConfiguration(supportHeartbeat: false),
- new MetricSourceConfiguration(DefaultHeartbeatInterval, new[] { MicrosoftAspNetCoreHostingEventSourceName })).GetProviders();
+ new AspNetTriggerSourceConfiguration(heartbeatIntervalSeconds: null),
+ new MetricSourceConfiguration(_heartbeatIntervalSeconds.Value, new[] { MicrosoftAspNetCoreHostingEventSourceName })).GetProviders();
}
else
{
private readonly IList<EventPipeProvider> _eventPipeProviders;
- public MetricSourceConfiguration(int metricIntervalSeconds, IEnumerable<string> customProviderNames)
+ public MetricSourceConfiguration(float metricIntervalSeconds, IEnumerable<string> customProviderNames)
{
if (customProviderNames == null)
{
private Dictionary<string, List<string>> _enabledCounters;
private int _intervalMilliseconds;
- public static CounterFilter AllCounters(int counterIntervalSeconds)
+ public static CounterFilter AllCounters(float counterIntervalSeconds)
=> new CounterFilter(counterIntervalSeconds);
- public CounterFilter(int intervalSeconds)
+ public CounterFilter(float intervalSeconds)
{
//Provider names are not case sensitive, but counter names are.
_enabledCounters = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase);
- _intervalMilliseconds = intervalSeconds * 1000;
+
+ //The Series payload of the counter which we use for filtering
+ _intervalMilliseconds = (int)(intervalSeconds * 1000);
}
// Called when we want to enable all counters under a provider name.
public IEnumerable<string> GetProviders() => _enabledCounters.Keys;
- public bool IsIncluded(string providerName, string counterName, int interval)
+ public bool IsIncluded(string providerName, string counterName, int intervalMilliseconds)
{
- if (_intervalMilliseconds != interval)
+ if (_intervalMilliseconds != intervalMilliseconds)
{
return false;
}
if (settings.CounterGroups.Length > 0)
{
- _filter = new CounterFilter(CounterIntervalSeconds);
+ _filter = new CounterFilter(Settings.CounterIntervalSeconds);
foreach (var counterGroup in settings.CounterGroups)
{
_filter.AddFilter(counterGroup.ProviderName, counterGroup.CounterNames);
}
else
{
- _filter = CounterFilter.AllCounters(CounterIntervalSeconds);
+ _filter = CounterFilter.AllCounters(Settings.CounterIntervalSeconds);
}
}
protected override MonitoringSourceConfiguration CreateConfiguration()
{
- return new MetricSourceConfiguration(CounterIntervalSeconds, _filter.GetProviders());
+ return new MetricSourceConfiguration(Settings.CounterIntervalSeconds, _filter.GetProviders());
}
protected override async Task OnEventSourceAvailable(EventPipeEventSource eventSource, Func<Task> stopSessionAsync, CancellationToken token)
}
}
}
-
- private int CounterIntervalSeconds => (int)Settings.RefreshInterval.TotalSeconds;
}
}
internal class EventPipeCounterPipelineSettings : EventSourcePipelineSettings
{
public EventPipeCounterGroup[] CounterGroups { get; set; }
- public TimeSpan RefreshInterval { get; set; }
+
+ //Do not use TimeSpan here since we may need to synchronize this pipeline interval
+ //with a different session and want to make sure the values are identical.
+ public float CounterIntervalSeconds { get; set; }
}
internal class EventPipeCounterGroup
//Make sure we are part of the requested series. If multiple clients request metrics, all of them get the metrics.
string series = payloadFields["Series"].ToString();
string counterName = payloadFields["Name"].ToString();
+
+ //CONSIDER
+ //Concurrent counter sessions do not each get a separate interval. Instead the payload
+ //for _all_ the counters changes the Series to be the lowest specified interval, on a per provider basis.
+ //Currently the CounterFilter will remove any data whose Series doesn't match the requested interval.
if (!filter.IsIncluded(traceEvent.ProviderName, counterName, GetInterval(series)))
{
return false;
{
private readonly long _durationTicks;
+ //Note that regardless of the metrics interval used, we will only update
+ //on a certain frequency to avoid unnecessary processing.
//This is adjusted due to rounding errors on event counter timestamp math.
- private readonly TimeSpan _heartBeatInterval = TimeSpan.FromSeconds(AspNetTriggerSourceConfiguration.DefaultHeartbeatInterval - 1);
+ private static readonly TimeSpan HeartbeatIntervalSeconds = TimeSpan.FromSeconds(9);
private SlidingWindow _window;
private Dictionary<string, DateTime> _requests = new();
private DateTime _lastHeartbeatProcessed = DateTime.MinValue;
{
//May get additional heartbeats based on multiple counters or extra intervals. We only
//process the data periodically.
- if (timestamp - _lastHeartbeatProcessed > _heartBeatInterval)
+ if (timestamp - _lastHeartbeatProcessed > HeartbeatIntervalSeconds)
{
_lastHeartbeatProcessed = timestamp;
List<string> requestsToRemove = new();
_valueFilter = value => value < maxValue;
}
- _intervalTicks = settings.CounterIntervalSeconds * TimeSpan.TicksPerSecond;
+ _intervalTicks = (long)(settings.CounterIntervalSeconds * TimeSpan.TicksPerSecond);
_windowTicks = settings.SlidingWindowDuration.Ticks;
}
internal sealed class EventCounterTriggerSettings :
IValidatableObject
{
- internal const int CounterIntervalSeconds_MaxValue = 24 * 60 * 60; // 1 day
- internal const int CounterIntervalSeconds_MinValue = 1; // 1 second
+ internal const float CounterIntervalSeconds_MaxValue = 24 * 60 * 60; // 1 day
+ internal const float CounterIntervalSeconds_MinValue = 1; // 1 second
internal const string EitherGreaterThanLessThanMessage = "Either the " + nameof(GreaterThan) + " field or the " + nameof(LessThan) + " field are required.";
/// The sampling interval of the event counter.
/// </summary>
[Range(CounterIntervalSeconds_MinValue, CounterIntervalSeconds_MaxValue)]
- public int CounterIntervalSeconds { get; set; }
+ public float CounterIntervalSeconds { get; set; }
IEnumerable<ValidationResult> IValidatableObject.Validate(ValidationContext validationContext)
{
CounterNames = expectedCounters
}
},
- RefreshInterval = TimeSpan.FromSeconds(1)
+ CounterIntervalSeconds = 1
}, new[] { logger });
await PipelineTestUtilities.ExecutePipelineWithDebugee(
/// </summary>
private sealed class CpuUsagePayloadFactory
{
- private readonly int _intervalSeconds;
+ private readonly float _intervalSeconds;
private readonly Random _random;
private DateTime? _lastTimestamp;
- public CpuUsagePayloadFactory(int seed, int intervalSeconds)
+ public CpuUsagePayloadFactory(int seed, float intervalSeconds)
{
_intervalSeconds = intervalSeconds;
_random = new Random(seed);