diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml
index 0212e2b3e6..b639323729 100644
--- a/eng/Version.Details.xml
+++ b/eng/Version.Details.xml
@@ -12,9 +12,9 @@
https://github.com/microsoft/clrmd
a64d9ac11086f28fbd4b2b2337c19be7826fbfa9
-
+
https://github.com/dotnet/source-build-reference-packages
- 7ec4b0aabc55efa11515c987fb0251d39c29f06c
+ bed0fd20a5a55843c1f04ef195ef2fd7171ae715
diff --git a/src/Microsoft.Diagnostics.Monitoring.EventPipe/Configuration/MetricSourceConfiguration.cs b/src/Microsoft.Diagnostics.Monitoring.EventPipe/Configuration/MetricSourceConfiguration.cs
index 4047e11242..1c3891f123 100644
--- a/src/Microsoft.Diagnostics.Monitoring.EventPipe/Configuration/MetricSourceConfiguration.cs
+++ b/src/Microsoft.Diagnostics.Monitoring.EventPipe/Configuration/MetricSourceConfiguration.cs
@@ -16,6 +16,7 @@ namespace Microsoft.Diagnostics.Monitoring.EventPipe
public sealed class MetricSourceConfiguration : MonitoringSourceConfiguration
{
private readonly IList _eventPipeProviders;
+ public string SessionId { get; private set; }
public MetricSourceConfiguration(float metricIntervalSeconds, IEnumerable customProviderNames)
{
@@ -45,6 +46,37 @@ public MetricSourceConfiguration(float metricIntervalSeconds, IEnumerable customProviderNames, int maxHistograms, int maxTimeSeries) : this(metricIntervalSeconds, customProviderNames)
+ {
+ const long TimeSeriesValues = 0x2;
+ StringBuilder metrics = new StringBuilder();
+ foreach (string provider in customProviderNames)
+ {
+ if (metrics.Length != 0)
+ {
+ metrics.Append(",");
+ }
+
+ metrics.Append(provider);
+ }
+
+ SessionId = Guid.NewGuid().ToString();
+
+ EventPipeProvider metricsEventSourceProvider =
+ new EventPipeProvider("System.Diagnostics.Metrics", EventLevel.Informational, TimeSeriesValues,
+ new Dictionary()
+ {
+ { "SessionId", SessionId },
+ { "Metrics", metrics.ToString() },
+ { "RefreshInterval", MetricIntervalSeconds.ToString() },
+ { "MaxTimeSeries", maxTimeSeries.ToString() },
+ { "MaxHistograms", maxHistograms.ToString() }
+ }
+ );
+
+ _eventPipeProviders = _eventPipeProviders.Append(metricsEventSourceProvider).ToArray();
+ }
+
private string MetricIntervalSeconds { get; }
public override IList GetProviders() => _eventPipeProviders;
diff --git a/src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/CounterFilter.cs b/src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/CounterFilter.cs
index dd83243ef7..9ad08494d4 100644
--- a/src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/CounterFilter.cs
+++ b/src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/CounterFilter.cs
@@ -44,6 +44,12 @@ public bool IsIncluded(string providerName, string counterName, int intervalMill
{
return false;
}
+
+ return IsIncluded(providerName, counterName);
+ }
+
+ public bool IsIncluded(string providerName, string counterName)
+ {
if (_enabledCounters.Count == 0)
{
return true;
diff --git a/src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/CounterPayload.cs b/src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/CounterPayload.cs
index 0108e722bc..fc9f8a9485 100644
--- a/src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/CounterPayload.cs
+++ b/src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/CounterPayload.cs
@@ -8,7 +8,7 @@
namespace Microsoft.Diagnostics.Monitoring.EventPipe
{
- internal class CounterPayload : ICounterPayload
+ public class CounterPayload : ICounterPayload
{
#if NETSTANDARD
private static readonly IReadOnlyDictionary Empty = new ReadOnlyDictionary(new Dictionary(0));
@@ -35,13 +35,26 @@ public CounterPayload(DateTime timestamp,
Provider = provider;
Interval = interval;
Metadata = metadata ?? Empty;
+ EventType = EventType.Gauge;
+ }
+
+ // Copied from dotnet-counters
+ public CounterPayload(string providerName, string name, string displayName, string displayUnits, Dictionary metadata, double value, DateTime timestamp, string type, EventType eventType)
+ {
+ Provider = providerName;
+ Name = name;
+ Metadata = metadata ?? Empty;
+ Value = value;
+ Timestamp = timestamp;
+ CounterType = (CounterType)Enum.Parse(typeof(CounterType), type);
+ EventType = eventType;
}
public string Namespace { get; }
public string Name { get; }
- public string DisplayName { get; }
+ public string DisplayName { get; protected set; }
public string Unit { get; }
@@ -56,5 +69,63 @@ public CounterPayload(DateTime timestamp,
public string Provider { get; }
public IReadOnlyDictionary Metadata { get; }
+
+ public EventType EventType { get; set; }
+
+ }
+
+ public class GaugePayload : CounterPayload
+ {
+ public GaugePayload(string providerName, string name, string displayName, string displayUnits, Dictionary metadata, double value, DateTime timestamp) :
+ base(providerName, name, displayName, displayUnits, metadata, value, timestamp, "Metric", EventType.Gauge)
+ {
+ // In case these properties are not provided, set them to appropriate values.
+ string counterName = string.IsNullOrEmpty(displayName) ? name : displayName;
+ DisplayName = !string.IsNullOrEmpty(displayUnits) ? $"{counterName} ({displayUnits})" : counterName;
+ }
+ }
+
+ public class RatePayload : CounterPayload
+ {
+ public RatePayload(string providerName, string name, string displayName, string displayUnits, Dictionary metadata, double value, double intervalSecs, DateTime timestamp) :
+ base(providerName, name, displayName, displayUnits, metadata, value, timestamp, "Rate", EventType.Rate)
+ {
+ // In case these properties are not provided, set them to appropriate values.
+ string counterName = string.IsNullOrEmpty(displayName) ? name : displayName;
+ string unitsName = string.IsNullOrEmpty(displayUnits) ? "Count" : displayUnits;
+ string intervalName = intervalSecs.ToString() + " sec";
+ DisplayName = $"{counterName} ({unitsName} / {intervalName})";
+ }
+ }
+
+ public class PercentilePayload : CounterPayload
+ {
+ public PercentilePayload(string providerName, string name, string displayName, string displayUnits, Dictionary metadata, double val, DateTime timestamp) :
+ base(providerName, name, displayName, displayUnits, metadata, val, timestamp, "Metric", EventType.Histogram)
+ {
+ // In case these properties are not provided, set them to appropriate values.
+ string counterName = string.IsNullOrEmpty(displayName) ? name : displayName;
+ DisplayName = !string.IsNullOrEmpty(displayUnits) ? $"{counterName} ({displayUnits})" : counterName;
+ }
+ }
+
+ public class ErrorPayload : CounterPayload
+ {
+ public ErrorPayload(string providerName, string name, string displayName, string displayUnits, Dictionary metadata, double val, DateTime timestamp, string errorMessage) :
+ base(providerName, name, displayName, displayUnits, metadata, val, timestamp, "Metric", EventType.Error)
+ {
+ ErrorMessage = errorMessage;
+ }
+
+ public string ErrorMessage { get; private set; }
+ }
+
+ // If keep this, should probably put it somewhere else
+ public enum EventType : int
+ {
+ Rate,
+ Gauge,
+ Histogram,
+ Error
}
}
\ No newline at end of file
diff --git a/src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/EventCounterPipeline.cs b/src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/EventCounterPipeline.cs
index 14cdfe5b9f..280c552f58 100644
--- a/src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/EventCounterPipeline.cs
+++ b/src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/EventCounterPipeline.cs
@@ -15,6 +15,7 @@ internal class EventCounterPipeline : EventSourcePipeline _loggers;
private readonly CounterFilter _filter;
+ private string _sessionId;
public EventCounterPipeline(DiagnosticsClient client,
EventPipeCounterPipelineSettings settings,
@@ -38,7 +39,11 @@ public EventCounterPipeline(DiagnosticsClient client,
protected override MonitoringSourceConfiguration CreateConfiguration()
{
- return new MetricSourceConfiguration(Settings.CounterIntervalSeconds, _filter.GetProviders());
+ var config = new MetricSourceConfiguration(Settings.CounterIntervalSeconds, _filter.GetProviders(), Settings.MaxHistograms, Settings.MaxTimeSeries);
+
+ _sessionId = config.SessionId;
+
+ return config;
}
protected override async Task OnEventSourceAvailable(EventPipeEventSource eventSource, Func stopSessionAsync, CancellationToken token)
@@ -49,7 +54,7 @@ protected override async Task OnEventSourceAvailable(EventPipeEventSource eventS
{
try
{
- if (traceEvent.TryGetCounterPayload(_filter, out ICounterPayload counterPayload))
+ if (traceEvent.TryGetCounterPayload(_filter, _sessionId, out List counterPayload))
{
ExecuteCounterLoggerAction((metricLogger) => metricLogger.Log(counterPayload));
}
diff --git a/src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/EventPipeCounterPipelineSettings.cs b/src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/EventPipeCounterPipelineSettings.cs
index ffcb93924a..9aa3855634 100644
--- a/src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/EventPipeCounterPipelineSettings.cs
+++ b/src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/EventPipeCounterPipelineSettings.cs
@@ -15,6 +15,10 @@ internal class EventPipeCounterPipelineSettings : EventSourcePipelineSettings
//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; }
+
+ public int MaxHistograms { get; set; }
+
+ public int MaxTimeSeries { get; set; }
}
internal class EventPipeCounterGroup
diff --git a/src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/ICounterPayload.cs b/src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/ICounterPayload.cs
index b492a629bb..cc27e69d9b 100644
--- a/src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/ICounterPayload.cs
+++ b/src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/ICounterPayload.cs
@@ -8,7 +8,7 @@
namespace Microsoft.Diagnostics.Monitoring.EventPipe
{
- internal enum CounterType
+ public enum CounterType
{
//Same as average or mean
Metric,
@@ -17,7 +17,7 @@ internal enum CounterType
Rate
}
- internal interface ICounterPayload
+ public interface ICounterPayload
{
string Name { get; }
@@ -36,5 +36,7 @@ internal interface ICounterPayload
float Interval { get; }
IReadOnlyDictionary Metadata { get; }
+
+ public EventType EventType { get; set; }
}
}
diff --git a/src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/ICountersLogger.cs b/src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/ICountersLogger.cs
index 8a522c9fe9..b0ddf1f753 100644
--- a/src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/ICountersLogger.cs
+++ b/src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/ICountersLogger.cs
@@ -14,7 +14,7 @@ internal interface ICountersLogger
{
//TODO Consider making these async.
- void Log(ICounterPayload counter);
+ void Log(List counter);
void PipelineStarted();
void PipelineStopped();
}
diff --git a/src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/TraceEventExtensions.cs b/src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/TraceEventExtensions.cs
index f0ec5c6310..96c531f3e7 100644
--- a/src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/TraceEventExtensions.cs
+++ b/src/Microsoft.Diagnostics.Monitoring.EventPipe/Counters/TraceEventExtensions.cs
@@ -5,14 +5,15 @@
using Microsoft.Diagnostics.Tracing;
using System;
using System.Collections.Generic;
+using System.Linq;
namespace Microsoft.Diagnostics.Monitoring.EventPipe
{
internal static class TraceEventExtensions
{
- public static bool TryGetCounterPayload(this TraceEvent traceEvent, CounterFilter filter, out ICounterPayload payload)
+ public static bool TryGetCounterPayload(this TraceEvent traceEvent, CounterFilter filter, string sessionId, out List payload)
{
- payload = null;
+ payload = new List();
if ("EventCounters".Equals(traceEvent.EventName))
{
@@ -57,7 +58,8 @@ public static bool TryGetCounterPayload(this TraceEvent traceEvent, CounterFilte
// Note that dimensional data such as pod and namespace are automatically added in prometheus and azure monitor scenarios.
// We no longer added it here.
- payload = new CounterPayload(
+
+ payload.Add(new CounterPayload(
traceEvent.TimeStamp,
traceEvent.ProviderName,
counterName, displayName,
@@ -65,13 +67,247 @@ public static bool TryGetCounterPayload(this TraceEvent traceEvent, CounterFilte
value,
counterType,
intervalSec,
- metadataDict);
+ metadataDict));
+
return true;
}
+ if (sessionId != null && "System.Diagnostics.Metrics".Equals(traceEvent.ProviderName))
+ {
+
+ ICounterPayload individualPayload = null;
+
+ if (traceEvent.EventName == "BeginInstrumentReporting")
+ {
+ // Do we want to log something for this?
+ //HandleBeginInstrumentReporting(traceEvent);
+ }
+ if (traceEvent.EventName == "HistogramValuePublished")
+ {
+ HandleHistogram(traceEvent, filter, sessionId, out payload);
+ }
+ else if (traceEvent.EventName == "GaugeValuePublished")
+ {
+ HandleGauge(traceEvent, filter, sessionId, out individualPayload);
+ }
+ else if (traceEvent.EventName == "CounterRateValuePublished")
+ {
+ HandleCounterRate(traceEvent, filter, sessionId, out individualPayload);
+ }
+ else if (traceEvent.EventName == "TimeSeriesLimitReached")
+ {
+ HandleTimeSeriesLimitReached(traceEvent, sessionId, out individualPayload);
+ }
+ else if (traceEvent.EventName == "HistogramLimitReached")
+ {
+ HandleHistogramLimitReached(traceEvent, sessionId, out individualPayload);
+ }
+ else if (traceEvent.EventName == "Error")
+ {
+ HandleError(traceEvent, sessionId, out individualPayload);
+ }
+ else if (traceEvent.EventName == "ObservableInstrumentCallbackError")
+ {
+ HandleObservableInstrumentCallbackError(traceEvent, sessionId, out individualPayload);
+ }
+ else if (traceEvent.EventName == "MultipleSessionsNotSupportedError")
+ {
+ HandleMultipleSessionsNotSupportedError(traceEvent, sessionId, out individualPayload);
+ }
+
+ if (null != individualPayload)
+ {
+ payload.Add(individualPayload);
+ }
+
+ return null != payload && payload.Any();
+ }
+
return false;
}
+ public static bool TryGetCounterPayload(this TraceEvent traceEvent, CounterFilter filter, out List payload)
+ {
+ return TryGetCounterPayload(traceEvent, filter, null, out payload);
+ }
+
+ private static void HandleGauge(TraceEvent obj, CounterFilter filter, string sessionId, out ICounterPayload payload)
+ {
+ payload = null;
+
+ string payloadSessionId = (string)obj.PayloadValue(0);
+
+ if (payloadSessionId != sessionId)
+ {
+ return;
+ }
+
+ string meterName = (string)obj.PayloadValue(1);
+ //string meterVersion = (string)obj.PayloadValue(2);
+ string instrumentName = (string)obj.PayloadValue(3);
+ string unit = (string)obj.PayloadValue(4);
+ string tags = (string)obj.PayloadValue(5);
+ string lastValueText = (string)obj.PayloadValue(6);
+
+ Dictionary metadataDict = GetMetadata(tags);
+
+ if (!filter.IsIncluded(meterName, instrumentName))
+ {
+ return;
+ }
+
+ // the value might be an empty string indicating no measurement was provided this collection interval
+ if (double.TryParse(lastValueText, out double lastValue))
+ {
+ payload = new GaugePayload(meterName, instrumentName, null, unit, metadataDict, lastValue, obj.TimeStamp);
+ }
+ }
+
+ private static void HandleCounterRate(TraceEvent traceEvent, CounterFilter filter, string sessionId, out ICounterPayload payload)
+ {
+ payload = null;
+
+ string payloadSessionId = (string)traceEvent.PayloadValue(0);
+
+ if (payloadSessionId != sessionId)
+ {
+ return;
+ }
+
+ string meterName = (string)traceEvent.PayloadValue(1);
+ //string meterVersion = (string)obj.PayloadValue(2);
+ string instrumentName = (string)traceEvent.PayloadValue(3);
+ string unit = (string)traceEvent.PayloadValue(4);
+ string tags = (string)traceEvent.PayloadValue(5);
+ string rateText = (string)traceEvent.PayloadValue(6);
+
+ Dictionary metadataDict = GetMetadata(tags);
+
+ if (double.TryParse(rateText, out double rate))
+ {
+ payload = new RatePayload(meterName, instrumentName, null, unit, metadataDict, rate, 10, traceEvent.TimeStamp);
+ }
+ }
+
+ private static void HandleHistogram(TraceEvent obj, CounterFilter filter, string sessionId, out List payload)
+ {
+ payload = new List();
+
+ string payloadSessionId = (string)obj.PayloadValue(0);
+
+ if (payloadSessionId != sessionId)
+ {
+ return;
+ }
+
+ string meterName = (string)obj.PayloadValue(1);
+ //string meterVersion = (string)obj.PayloadValue(2);
+ string instrumentName = (string)obj.PayloadValue(3);
+ string unit = (string)obj.PayloadValue(4);
+ string tags = (string)obj.PayloadValue(5);
+ string quantilesText = (string)obj.PayloadValue(6);
+
+ if (!filter.IsIncluded(meterName, instrumentName))
+ {
+ return;
+ }
+
+ KeyValuePair[] quantiles = ParseQuantiles(quantilesText);
+ foreach ((double key, double val) in quantiles)
+ {
+ Dictionary metadataDict = GetMetadata(tags);
+ metadataDict.Add("quantile", key.ToString());
+ payload.Add(new PercentilePayload(meterName, instrumentName, null, unit, metadataDict, val, obj.TimeStamp));
+ }
+ }
+
+ private static void HandleHistogramLimitReached(TraceEvent obj, string sessionId, out ICounterPayload payload)
+ {
+ payload = null;
+
+ string payloadSessionId = (string)obj.PayloadValue(0);
+
+ if (payloadSessionId != sessionId)
+ {
+ return;
+ }
+
+ string errorMessage = $"Warning: Histogram tracking limit reached. Not all data is being shown. The limit can be changed with maxHistograms but will use more memory in the target process.";
+
+ payload = new ErrorPayload(string.Empty, string.Empty, string.Empty, string.Empty, new(), 0, obj.TimeStamp, errorMessage);
+ }
+
+ private static void HandleTimeSeriesLimitReached(TraceEvent obj, string sessionId, out ICounterPayload payload)
+ {
+ payload = null;
+
+ string payloadSessionId = (string)obj.PayloadValue(0);
+
+ if (payloadSessionId != sessionId)
+ {
+ return;
+ }
+
+ string errorMessage = "Warning: Time series tracking limit reached. Not all data is being shown. The limit can be changed with maxTimeSeries but will use more memory in the target process.";
+
+ payload = new ErrorPayload(string.Empty, string.Empty, string.Empty, string.Empty, new(), 0, obj.TimeStamp, errorMessage);
+ }
+
+ private static void HandleError(TraceEvent obj, string sessionId, out ICounterPayload payload)
+ {
+ payload = null;
+
+ string payloadSessionId = (string)obj.PayloadValue(0);
+ string error = (string)obj.PayloadValue(1);
+ if (sessionId != payloadSessionId)
+ {
+ return;
+ }
+
+ string errorMessage = "Error reported from target process:" + Environment.NewLine + error;
+
+ payload = new ErrorPayload(string.Empty, string.Empty, string.Empty, string.Empty, new(), 0, obj.TimeStamp, errorMessage);
+ }
+
+ private static void HandleMultipleSessionsNotSupportedError(TraceEvent obj, string sessionId, out ICounterPayload payload)
+ {
+ payload = null;
+
+ string payloadSessionId = (string)obj.PayloadValue(0);
+ if (payloadSessionId == sessionId)
+ {
+ // If our session is the one that is running then the error is not for us,
+ // it is for some other session that came later
+ return;
+ }
+ else
+ {
+ string errorMessage = "Error: Another metrics collection session is already in progress for the target process, perhaps from another tool? " + Environment.NewLine +
+ "Concurrent sessions are not supported.";
+
+ payload = new ErrorPayload(string.Empty, string.Empty, string.Empty, string.Empty, new(), 0, obj.TimeStamp, errorMessage);
+ }
+ }
+
+ private static void HandleObservableInstrumentCallbackError(TraceEvent obj, string sessionId, out ICounterPayload payload)
+ {
+ payload = null;
+
+ string payloadSessionId = (string)obj.PayloadValue(0);
+ string error = (string)obj.PayloadValue(1);
+
+ if (payloadSessionId != sessionId)
+ {
+ return;
+ }
+
+ string errorMessage = "Exception thrown from an observable instrument callback in the target process:" + Environment.NewLine +
+ error;
+
+ payload = new ErrorPayload(string.Empty, string.Empty, string.Empty, string.Empty, new(), 0, obj.TimeStamp, errorMessage);
+ }
+
+
//The metadata payload is formatted as a string of comma separated key:value pairs.
//This limitation means that metadata values cannot include commas; otherwise, the
//metadata will be parsed incorrectly. If a value contains a comma, then all metadata
@@ -114,6 +350,30 @@ internal static Dictionary GetMetadata(string metadataPayload)
return metadataDict;
}
+ private static KeyValuePair[] ParseQuantiles(string quantileList)
+ {
+ string[] quantileParts = quantileList.Split(';', StringSplitOptions.RemoveEmptyEntries);
+ List> quantiles = new List>();
+ foreach (string quantile in quantileParts)
+ {
+ string[] keyValParts = quantile.Split('=', StringSplitOptions.RemoveEmptyEntries);
+ if (keyValParts.Length != 2)
+ {
+ continue;
+ }
+ if (!double.TryParse(keyValParts[0], out double key))
+ {
+ continue;
+ }
+ if (!double.TryParse(keyValParts[1], out double val))
+ {
+ continue;
+ }
+ quantiles.Add(new KeyValuePair(key, val));
+ }
+ return quantiles.ToArray();
+ }
+
private static int GetInterval(string series)
{
const string comparison = "Interval=";
diff --git a/src/Microsoft.Diagnostics.Monitoring.EventPipe/Triggers/EventCounter/EventCounterTrigger.cs b/src/Microsoft.Diagnostics.Monitoring.EventPipe/Triggers/EventCounter/EventCounterTrigger.cs
index 622171d07c..51fd9f3de8 100644
--- a/src/Microsoft.Diagnostics.Monitoring.EventPipe/Triggers/EventCounter/EventCounterTrigger.cs
+++ b/src/Microsoft.Diagnostics.Monitoring.EventPipe/Triggers/EventCounter/EventCounterTrigger.cs
@@ -59,9 +59,9 @@ public IReadOnlyDictionary> GetProviderEvent
public bool HasSatisfiedCondition(TraceEvent traceEvent)
{
// Filter to the counter of interest before forwarding to the implementation
- if (traceEvent.TryGetCounterPayload(_filter, out ICounterPayload payload))
+ if (traceEvent.TryGetCounterPayload(_filter, out List payload))
{
- return _impl.HasSatisfiedCondition(payload);
+ return _impl.HasSatisfiedCondition(payload[0]); // Need to check if this is safe - in theory just want the first (and only) result
}
return false;
}