diff --git a/sdk/monitor/azure-monitor-opentelemetry-autoconfigure/checkstyle-suppressions.xml b/sdk/monitor/azure-monitor-opentelemetry-autoconfigure/checkstyle-suppressions.xml
index 766caf41d4093..227fa9311cd16 100644
--- a/sdk/monitor/azure-monitor-opentelemetry-autoconfigure/checkstyle-suppressions.xml
+++ b/sdk/monitor/azure-monitor-opentelemetry-autoconfigure/checkstyle-suppressions.xml
@@ -235,6 +235,17 @@
+
+
+
+
+
+
+
+
+
+
+
@@ -276,6 +287,7 @@
+
@@ -339,7 +351,15 @@
+
+
+
+
+
+
+
+
diff --git a/sdk/monitor/azure-monitor-opentelemetry-autoconfigure/spotbugs-exclude.xml b/sdk/monitor/azure-monitor-opentelemetry-autoconfigure/spotbugs-exclude.xml
index 5d6697f82d1b8..ab19b1b35f78a 100644
--- a/sdk/monitor/azure-monitor-opentelemetry-autoconfigure/spotbugs-exclude.xml
+++ b/sdk/monitor/azure-monitor-opentelemetry-autoconfigure/spotbugs-exclude.xml
@@ -37,6 +37,7 @@
+
@@ -98,5 +99,9 @@
+
+
+
+
diff --git a/sdk/monitor/azure-monitor-opentelemetry-autoconfigure/src/main/java/com/azure/monitor/opentelemetry/autoconfigure/implementation/quickpulse/QuickPulseDataCollector.java b/sdk/monitor/azure-monitor-opentelemetry-autoconfigure/src/main/java/com/azure/monitor/opentelemetry/autoconfigure/implementation/quickpulse/QuickPulseDataCollector.java
index 39d69289f0f87..5121c7259581c 100644
--- a/sdk/monitor/azure-monitor-opentelemetry-autoconfigure/src/main/java/com/azure/monitor/opentelemetry/autoconfigure/implementation/quickpulse/QuickPulseDataCollector.java
+++ b/sdk/monitor/azure-monitor-opentelemetry-autoconfigure/src/main/java/com/azure/monitor/opentelemetry/autoconfigure/implementation/quickpulse/QuickPulseDataCollector.java
@@ -163,6 +163,7 @@ private void addException(TelemetryExceptionData exceptionData, int itemCount) {
counters.exceptions.addAndGet(itemCount);
List exceptionList = exceptionData.getExceptions();
+ // Exception is a class from live metrics swagger that represents a document for an exception
Exception exceptionDoc = new Exception();
if (exceptionList != null && !exceptionList.isEmpty()) {
exceptionDoc.setExceptionMessage(exceptionList.get(0).getMessage());
diff --git a/sdk/monitor/azure-monitor-opentelemetry-autoconfigure/src/main/java/com/azure/monitor/opentelemetry/autoconfigure/implementation/quickpulse/filtering/CustomDimensions.java b/sdk/monitor/azure-monitor-opentelemetry-autoconfigure/src/main/java/com/azure/monitor/opentelemetry/autoconfigure/implementation/quickpulse/filtering/CustomDimensions.java
new file mode 100644
index 0000000000000..30fdeb0dd896c
--- /dev/null
+++ b/sdk/monitor/azure-monitor-opentelemetry-autoconfigure/src/main/java/com/azure/monitor/opentelemetry/autoconfigure/implementation/quickpulse/filtering/CustomDimensions.java
@@ -0,0 +1,31 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+package com.azure.monitor.opentelemetry.autoconfigure.implementation.quickpulse.filtering;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class CustomDimensions {
+ private Map customDimensions;
+
+ public CustomDimensions() {
+ this.customDimensions = new HashMap();
+ }
+
+ public void setCustomDimensions(Map customDimensions, Map customMeasurements) {
+ Map resultMap = new HashMap<>();
+ if (customDimensions != null) {
+ resultMap.putAll(customDimensions);
+ }
+ if (customMeasurements != null) {
+ for (Map.Entry cmEntry : customMeasurements.entrySet()) {
+ resultMap.put(cmEntry.getKey(), cmEntry.getValue().toString());
+ }
+ }
+ this.customDimensions = resultMap;
+ }
+
+ public Map getCustomDimensions() {
+ return this.customDimensions;
+ }
+}
diff --git a/sdk/monitor/azure-monitor-opentelemetry-autoconfigure/src/main/java/com/azure/monitor/opentelemetry/autoconfigure/implementation/quickpulse/filtering/DependencyDataColumns.java b/sdk/monitor/azure-monitor-opentelemetry-autoconfigure/src/main/java/com/azure/monitor/opentelemetry/autoconfigure/implementation/quickpulse/filtering/DependencyDataColumns.java
new file mode 100644
index 0000000000000..b8cea69356744
--- /dev/null
+++ b/sdk/monitor/azure-monitor-opentelemetry-autoconfigure/src/main/java/com/azure/monitor/opentelemetry/autoconfigure/implementation/quickpulse/filtering/DependencyDataColumns.java
@@ -0,0 +1,76 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package com.azure.monitor.opentelemetry.autoconfigure.implementation.quickpulse.filtering;
+
+import com.azure.monitor.opentelemetry.autoconfigure.implementation.models.RemoteDependencyData;
+import com.azure.monitor.opentelemetry.autoconfigure.implementation.utils.FormattedDuration;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+// Casing of private fields is to match the names of fields passed down via filtering configuration
+public class DependencyDataColumns implements TelemetryColumns {
+ private final CustomDimensions customDims;
+ private final Map mapping = new HashMap<>();
+
+ public DependencyDataColumns(RemoteDependencyData rdData) {
+ customDims = new CustomDimensions();
+ customDims.setCustomDimensions(rdData.getProperties(), rdData.getMeasurements());
+ mapping.put(KnownDependencyColumns.TARGET, rdData.getTarget());
+ mapping.put(KnownDependencyColumns.DURATION,
+ FormattedDuration.getDurationFromTelemetryItemDurationString(rdData.getDuration()));
+ mapping.put(KnownDependencyColumns.SUCCESS, rdData.isSuccess());
+ mapping.put(KnownDependencyColumns.NAME, rdData.getName());
+ int resultCode;
+ try {
+ resultCode = Integer.parseInt(rdData.getResultCode());
+ } catch (NumberFormatException e) {
+ resultCode = -1;
+ }
+ mapping.put(KnownDependencyColumns.RESULT_CODE, resultCode);
+ mapping.put(KnownDependencyColumns.TYPE, rdData.getType());
+ mapping.put(KnownDependencyColumns.DATA, rdData.getData());
+ }
+
+ // To be used for tests only
+ public DependencyDataColumns(String target, long duration, boolean success, String name, int resultCode,
+ String type, String data, Map dims, Map measurements) {
+ customDims = new CustomDimensions();
+ customDims.setCustomDimensions(dims, measurements);
+ mapping.put(KnownDependencyColumns.TARGET, target);
+ mapping.put(KnownDependencyColumns.DURATION, duration);
+ mapping.put(KnownDependencyColumns.SUCCESS, success);
+ mapping.put(KnownDependencyColumns.NAME, name);
+ mapping.put(KnownDependencyColumns.RESULT_CODE, resultCode);
+ mapping.put(KnownDependencyColumns.TYPE, type);
+ mapping.put(KnownDependencyColumns.DATA, data);
+ }
+
+ public T getFieldValue(String fieldName, Class type) {
+ return type.cast(mapping.get(fieldName));
+ }
+
+ public Map getCustomDimensions() {
+ return this.customDims.getCustomDimensions();
+ }
+
+ public List getAllFieldValuesAsString() {
+ List result = new ArrayList<>();
+ for (Object value : mapping.values()) {
+ if (value instanceof String) {
+ result.add((String) value);
+ } else if (value instanceof Integer) {
+ result.add(((Integer) value).toString());
+ } else if (value instanceof Long) {
+ result.add(((Long) value).toString());
+ } else { // boolean
+ result.add(((Boolean) value).toString());
+ }
+ }
+ return result;
+ }
+
+}
diff --git a/sdk/monitor/azure-monitor-opentelemetry-autoconfigure/src/main/java/com/azure/monitor/opentelemetry/autoconfigure/implementation/quickpulse/filtering/ExceptionDataColumns.java b/sdk/monitor/azure-monitor-opentelemetry-autoconfigure/src/main/java/com/azure/monitor/opentelemetry/autoconfigure/implementation/quickpulse/filtering/ExceptionDataColumns.java
new file mode 100644
index 0000000000000..8ecbaf00e7714
--- /dev/null
+++ b/sdk/monitor/azure-monitor-opentelemetry-autoconfigure/src/main/java/com/azure/monitor/opentelemetry/autoconfigure/implementation/quickpulse/filtering/ExceptionDataColumns.java
@@ -0,0 +1,55 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package com.azure.monitor.opentelemetry.autoconfigure.implementation.quickpulse.filtering;
+
+import com.azure.monitor.opentelemetry.autoconfigure.implementation.models.TelemetryExceptionData;
+import com.azure.monitor.opentelemetry.autoconfigure.implementation.models.TelemetryExceptionDetails;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+// Casing of private fields is to match the names of fields passed down via filtering configuration
+public class ExceptionDataColumns implements TelemetryColumns {
+
+ private final CustomDimensions customDims;
+
+ private final Map mapping = new HashMap<>();
+
+ public ExceptionDataColumns(TelemetryExceptionData exceptionData) {
+ customDims = new CustomDimensions();
+ customDims.setCustomDimensions(exceptionData.getProperties(), exceptionData.getMeasurements());
+ List details = exceptionData.getExceptions();
+ mapping.put(KnownExceptionColumns.MESSAGE,
+ details != null && !details.isEmpty() ? details.get(0).getMessage() : "");
+ mapping.put(KnownExceptionColumns.STACK,
+ details != null && !details.isEmpty() ? details.get(0).getStack() : "");
+ }
+
+ // To be used in tests only
+ public ExceptionDataColumns(String message, String stackTrace, Map dims,
+ Map measurements) {
+ customDims = new CustomDimensions();
+ customDims.setCustomDimensions(dims, measurements);
+ mapping.put(KnownExceptionColumns.MESSAGE, message);
+ mapping.put(KnownExceptionColumns.STACK, stackTrace);
+ }
+
+ public Map getCustomDimensions() {
+ return this.customDims.getCustomDimensions();
+ }
+
+ public T getFieldValue(String fieldName, Class type) {
+ return type.cast(mapping.get(fieldName));
+ }
+
+ public List getAllFieldValuesAsString() {
+ List result = new ArrayList<>();
+ for (Object value : mapping.values()) {
+ result.add((String) value);
+ }
+ return result;
+ }
+}
diff --git a/sdk/monitor/azure-monitor-opentelemetry-autoconfigure/src/main/java/com/azure/monitor/opentelemetry/autoconfigure/implementation/quickpulse/filtering/Filter.java b/sdk/monitor/azure-monitor-opentelemetry-autoconfigure/src/main/java/com/azure/monitor/opentelemetry/autoconfigure/implementation/quickpulse/filtering/Filter.java
new file mode 100644
index 0000000000000..fd3c2cf454842
--- /dev/null
+++ b/sdk/monitor/azure-monitor-opentelemetry-autoconfigure/src/main/java/com/azure/monitor/opentelemetry/autoconfigure/implementation/quickpulse/filtering/Filter.java
@@ -0,0 +1,185 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package com.azure.monitor.opentelemetry.autoconfigure.implementation.quickpulse.filtering;
+
+import com.azure.monitor.opentelemetry.autoconfigure.implementation.quickpulse.swagger.models.DerivedMetricInfo;
+import com.azure.monitor.opentelemetry.autoconfigure.implementation.quickpulse.swagger.models.FilterConjunctionGroupInfo;
+import com.azure.monitor.opentelemetry.autoconfigure.implementation.quickpulse.swagger.models.FilterInfo;
+import com.azure.monitor.opentelemetry.autoconfigure.implementation.quickpulse.swagger.models.PredicateType;
+
+import java.util.List;
+import java.util.Map;
+
+public class Filter {
+ public static final String CUSTOM_DIM_FIELDNAME_PREFIX = "CustomDimensions.";
+ public static final String ANY_FIELD = "*";
+
+ // To be used when checking telemetry against metric charts filters
+ public static boolean checkMetricFilters(DerivedMetricInfo derivedMetricInfo, TelemetryColumns data) {
+ if (derivedMetricInfo.getFilterGroups().isEmpty()) {
+ // This should never happen - even when a user does not add filter pills to the derived metric,
+ // the filterGroups array should have one filter group with an empty array of filters.
+ return true;
+ }
+
+ // Haven't yet seen any case where there is more than one filter group in a derived metric info.
+ // Just to be safe, handling the multiple filter conjunction group case as an or operation.
+ boolean matched = false;
+ for (FilterConjunctionGroupInfo filterGroup : derivedMetricInfo.getFilterGroups()) {
+ matched = matched || checkFilterConjunctionGroup(filterGroup, data);
+ }
+ return matched;
+ }
+
+ // To be used when checking telemetry against document filters. This also gets reused in the logic for checking metrics
+ // charts filters.
+ public static boolean checkFilterConjunctionGroup(FilterConjunctionGroupInfo filterConjunctionGroupInfo,
+ TelemetryColumns data) {
+ // All of the filters need to match for this to return true (and operation).
+ for (FilterInfo filter : filterConjunctionGroupInfo.getFilters()) {
+ if (!checkFilter(filter, data)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static boolean checkFilter(FilterInfo filter, TelemetryColumns data) {
+ if (ANY_FIELD.equals(filter.getFieldName())) {
+ return checkAnyFieldFilter(filter, data);
+ } else if (filter.getFieldName().startsWith(CUSTOM_DIM_FIELDNAME_PREFIX)) {
+ return checkCustomDimFilter(filter, data);
+ } else {
+ String fieldName = filter.getFieldName();
+
+ if (filter.getFieldName().equals(KnownRequestColumns.SUCCESS)) {
+ boolean fieldValueBoolean = data.getFieldValue(filter.getFieldName(), Boolean.class);
+ boolean comparand = Boolean.parseBoolean(filter.getComparand().toLowerCase());
+ if (filter.getPredicate().equals(PredicateType.EQUAL)) {
+ return fieldValueBoolean == comparand;
+ } else if (filter.getPredicate().equals(PredicateType.NOT_EQUAL)) {
+ return fieldValueBoolean != comparand;
+ }
+ } else if (filter.getFieldName().equals(KnownDependencyColumns.DURATION)) {
+ long comparand = getMicroSecondsFromFilterTimestampString(filter.getComparand());
+ long fieldValueLong = data.getFieldValue(KnownRequestColumns.DURATION, Long.class);
+ return numericCompare(fieldValueLong, comparand, filter.getPredicate());
+ } else if (filter.getFieldName().equals(KnownDependencyColumns.RESULT_CODE)
+ || filter.getFieldName().equals(KnownRequestColumns.RESPONSE_CODE)) {
+ int comparand = Integer.parseInt(filter.getComparand());
+ PredicateType predicate = filter.getPredicate();
+ int fieldValueInt = data.getFieldValue(fieldName, Integer.class);
+ return numericCompare(fieldValueInt, comparand, predicate);
+ } else {
+ // string fields
+ String fieldValueString = data.getFieldValue(fieldName, String.class);
+ return stringCompare(fieldValueString, filter.getComparand(), filter.getPredicate());
+ }
+ }
+ return false;
+ }
+
+ private static boolean checkAnyFieldFilter(FilterInfo filter, TelemetryColumns data) {
+
+ List values = data.getAllFieldValuesAsString();
+ for (String value : values) {
+ if (stringCompare(value, filter.getComparand(), filter.getPredicate())) {
+ return true;
+ }
+ }
+ Map customDimensions = data.getCustomDimensions();
+ for (String value : customDimensions.values()) {
+ if (stringCompare(value, filter.getComparand(), filter.getPredicate())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static boolean checkCustomDimFilter(FilterInfo filter, TelemetryColumns data) {
+ Map customDimensions = data.getCustomDimensions();
+ String fieldName = filter.getFieldName().replace(CUSTOM_DIM_FIELDNAME_PREFIX, "");
+ if (customDimensions.containsKey(fieldName)) {
+ String value = customDimensions.get(fieldName);
+ return stringCompare(value, filter.getComparand(), filter.getPredicate());
+ } else {
+ return false; // the asked for field is not present in the custom dimensions
+ }
+ }
+
+ private static boolean stringCompare(String fieldValue, String comparand, PredicateType predicate) {
+ if (predicate.equals(PredicateType.EQUAL)) {
+ return fieldValue != null && fieldValue.equals(comparand);
+ } else if (predicate.equals(PredicateType.NOT_EQUAL)) {
+ return fieldValue != null && !fieldValue.equals(comparand);
+ } else if (predicate.equals(PredicateType.CONTAINS)) {
+ return fieldValue != null && fieldValue.toLowerCase().contains(comparand.toLowerCase());
+ } else if (predicate.equals(PredicateType.DOES_NOT_CONTAIN)) {
+ return fieldValue != null && !fieldValue.toLowerCase().contains(comparand.toLowerCase());
+ }
+ return false;
+ }
+
+ private static boolean numericCompare(long fieldValue, long comparand, PredicateType predicate) {
+ if (predicate.equals(PredicateType.EQUAL)) {
+ return fieldValue == comparand;
+ } else if (predicate.equals(PredicateType.NOT_EQUAL)) {
+ return fieldValue != comparand;
+ } else if (predicate.equals(PredicateType.GREATER_THAN)) {
+ return fieldValue > comparand;
+ } else if (predicate.equals(PredicateType.GREATER_THAN_OR_EQUAL)) {
+ return fieldValue >= comparand;
+ } else if (predicate.equals(PredicateType.LESS_THAN)) {
+ return fieldValue < comparand;
+ } else if (predicate.equals(PredicateType.LESS_THAN_OR_EQUAL)) {
+ return fieldValue <= comparand;
+ } else {
+ return false;
+ }
+ }
+
+ public static long getMicroSecondsFromFilterTimestampString(String timestamp) {
+ // The service side will return a timestamp in the following format:
+ // [days].[hours]:[minutes]:[seconds]
+ // the seconds may be a whole number or something like 7.89. 7.89 seconds translates to 7890000 microseconds.
+ // examples: "14.6:56:7.89" = 1234567890000 microseconds, "0.0:0:0.2" = 200000 microseconds
+
+ // Split the timestamp by ":"
+ String[] parts = timestamp.split(":");
+ if (parts.length != 3) {
+ return Long.MIN_VALUE; // Return a special value to indicate an error
+ }
+
+ // Parse seconds and minutes
+ long microseconds = (long) (Double.parseDouble(parts[2]) * 1000000L);
+ int minutes = parseInt(parts[1]);
+
+ // Split the first part by "." to get days and hours
+ String[] firstPart = parts[0].split("\\.");
+ if (firstPart.length != 2) {
+ return Long.MIN_VALUE; // Return a special value to indicate an error
+ }
+
+ int hours = parseInt(firstPart[1]);
+ int days = parseInt(firstPart[0]);
+
+ if (minutes == Integer.MIN_VALUE || hours == Integer.MIN_VALUE || days == Integer.MIN_VALUE) {
+ return Long.MIN_VALUE; // Return a special value to indicate an error
+ }
+
+ // Calculate the total microseconds
+ return microseconds + (minutes * 60L * 1000000L) + (hours * 60L * 60L * 1000000L)
+ + (days * 24L * 60L * 60L * 1000000L);
+
+ }
+
+ private static int parseInt(String value) {
+ try {
+ return Integer.parseInt(value);
+ } catch (NumberFormatException e) {
+ return Integer.MIN_VALUE;
+ }
+ }
+
+}
diff --git a/sdk/monitor/azure-monitor-opentelemetry-autoconfigure/src/main/java/com/azure/monitor/opentelemetry/autoconfigure/implementation/quickpulse/filtering/KnownDependencyColumns.java b/sdk/monitor/azure-monitor-opentelemetry-autoconfigure/src/main/java/com/azure/monitor/opentelemetry/autoconfigure/implementation/quickpulse/filtering/KnownDependencyColumns.java
new file mode 100644
index 0000000000000..1cbe55d84e10b
--- /dev/null
+++ b/sdk/monitor/azure-monitor-opentelemetry-autoconfigure/src/main/java/com/azure/monitor/opentelemetry/autoconfigure/implementation/quickpulse/filtering/KnownDependencyColumns.java
@@ -0,0 +1,14 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package com.azure.monitor.opentelemetry.autoconfigure.implementation.quickpulse.filtering;
+
+public class KnownDependencyColumns {
+ public static final String TARGET = "Target";
+ public static final String DURATION = "Duration";
+ public static final String RESULT_CODE = "ResultCode";
+ public static final String SUCCESS = "Success";
+ public static final String TYPE = "Type";
+ public static final String DATA = "Data";
+ public static final String NAME = "Name";
+}
diff --git a/sdk/monitor/azure-monitor-opentelemetry-autoconfigure/src/main/java/com/azure/monitor/opentelemetry/autoconfigure/implementation/quickpulse/filtering/KnownExceptionColumns.java b/sdk/monitor/azure-monitor-opentelemetry-autoconfigure/src/main/java/com/azure/monitor/opentelemetry/autoconfigure/implementation/quickpulse/filtering/KnownExceptionColumns.java
new file mode 100644
index 0000000000000..f8baa6dc6f7e9
--- /dev/null
+++ b/sdk/monitor/azure-monitor-opentelemetry-autoconfigure/src/main/java/com/azure/monitor/opentelemetry/autoconfigure/implementation/quickpulse/filtering/KnownExceptionColumns.java
@@ -0,0 +1,8 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+package com.azure.monitor.opentelemetry.autoconfigure.implementation.quickpulse.filtering;
+
+public class KnownExceptionColumns {
+ public static final String MESSAGE = "Exception.Message";
+ public static final String STACK = "Exception.StackTrace";
+}
diff --git a/sdk/monitor/azure-monitor-opentelemetry-autoconfigure/src/main/java/com/azure/monitor/opentelemetry/autoconfigure/implementation/quickpulse/filtering/KnownRequestColumns.java b/sdk/monitor/azure-monitor-opentelemetry-autoconfigure/src/main/java/com/azure/monitor/opentelemetry/autoconfigure/implementation/quickpulse/filtering/KnownRequestColumns.java
new file mode 100644
index 0000000000000..63a6c9f627a14
--- /dev/null
+++ b/sdk/monitor/azure-monitor-opentelemetry-autoconfigure/src/main/java/com/azure/monitor/opentelemetry/autoconfigure/implementation/quickpulse/filtering/KnownRequestColumns.java
@@ -0,0 +1,13 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package com.azure.monitor.opentelemetry.autoconfigure.implementation.quickpulse.filtering;
+
+public class KnownRequestColumns {
+ public static final String URL = "Url";
+ public static final String DURATION = "Duration";
+ public static final String RESPONSE_CODE = "ResponseCode";
+ public static final String SUCCESS = "Success";
+ public static final String NAME = "Name";
+
+}
diff --git a/sdk/monitor/azure-monitor-opentelemetry-autoconfigure/src/main/java/com/azure/monitor/opentelemetry/autoconfigure/implementation/quickpulse/filtering/KnownTraceColumns.java b/sdk/monitor/azure-monitor-opentelemetry-autoconfigure/src/main/java/com/azure/monitor/opentelemetry/autoconfigure/implementation/quickpulse/filtering/KnownTraceColumns.java
new file mode 100644
index 0000000000000..f5c5556dcdfe8
--- /dev/null
+++ b/sdk/monitor/azure-monitor-opentelemetry-autoconfigure/src/main/java/com/azure/monitor/opentelemetry/autoconfigure/implementation/quickpulse/filtering/KnownTraceColumns.java
@@ -0,0 +1,7 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+package com.azure.monitor.opentelemetry.autoconfigure.implementation.quickpulse.filtering;
+
+public class KnownTraceColumns {
+ public static final String MESSAGE = "Message";
+}
diff --git a/sdk/monitor/azure-monitor-opentelemetry-autoconfigure/src/main/java/com/azure/monitor/opentelemetry/autoconfigure/implementation/quickpulse/filtering/RequestDataColumns.java b/sdk/monitor/azure-monitor-opentelemetry-autoconfigure/src/main/java/com/azure/monitor/opentelemetry/autoconfigure/implementation/quickpulse/filtering/RequestDataColumns.java
new file mode 100644
index 0000000000000..2b265bcf299f8
--- /dev/null
+++ b/sdk/monitor/azure-monitor-opentelemetry-autoconfigure/src/main/java/com/azure/monitor/opentelemetry/autoconfigure/implementation/quickpulse/filtering/RequestDataColumns.java
@@ -0,0 +1,72 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package com.azure.monitor.opentelemetry.autoconfigure.implementation.quickpulse.filtering;
+
+import com.azure.monitor.opentelemetry.autoconfigure.implementation.models.RequestData;
+import com.azure.monitor.opentelemetry.autoconfigure.implementation.utils.FormattedDuration;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.HashMap;
+
+// Casing of private fields is to match the names of fields passed down via filtering configuration
+public class RequestDataColumns implements TelemetryColumns {
+ private final Map mapping = new HashMap<>();
+ private final CustomDimensions customDims;
+
+ public RequestDataColumns(RequestData requestData) {
+ customDims = new CustomDimensions();
+ customDims.setCustomDimensions(requestData.getProperties(), requestData.getMeasurements());
+ mapping.put(KnownRequestColumns.URL, requestData.getUrl());
+ mapping.put(KnownRequestColumns.SUCCESS, requestData.isSuccess());
+ mapping.put(KnownRequestColumns.DURATION,
+ FormattedDuration.getDurationFromTelemetryItemDurationString(requestData.getDuration()));
+ mapping.put(KnownRequestColumns.NAME, requestData.getName());
+ int responseCode;
+ try {
+ responseCode = Integer.parseInt(requestData.getResponseCode());
+ } catch (NumberFormatException e) {
+ responseCode = -1;
+ }
+ mapping.put(KnownRequestColumns.RESPONSE_CODE, responseCode);
+ }
+
+ // To be used in tests only
+ public RequestDataColumns(String url, long duration, int responseCode, boolean success, String name,
+ Map dims, Map measurements) {
+ customDims = new CustomDimensions();
+ customDims.setCustomDimensions(dims, measurements);
+ mapping.put(KnownRequestColumns.URL, url);
+ mapping.put(KnownRequestColumns.SUCCESS, success);
+ mapping.put(KnownRequestColumns.DURATION, duration);
+ mapping.put(KnownRequestColumns.NAME, name);
+ mapping.put(KnownRequestColumns.RESPONSE_CODE, responseCode);
+ }
+
+ public T getFieldValue(String fieldName, Class type) {
+ return type.cast(mapping.get(fieldName));
+ }
+
+ public List getAllFieldValuesAsString() {
+ List result = new ArrayList<>();
+ for (Object value : mapping.values()) {
+ if (value instanceof String) {
+ result.add((String) value);
+ } else if (value instanceof Integer) {
+ result.add(((Integer) value).toString());
+ } else if (value instanceof Long) {
+ result.add(((Long) value).toString());
+ } else { // boolean
+ result.add(((Boolean) value).toString());
+ }
+ }
+ return result;
+ }
+
+ public Map getCustomDimensions() {
+ return this.customDims.getCustomDimensions();
+ }
+
+}
diff --git a/sdk/monitor/azure-monitor-opentelemetry-autoconfigure/src/main/java/com/azure/monitor/opentelemetry/autoconfigure/implementation/quickpulse/filtering/TelemetryColumns.java b/sdk/monitor/azure-monitor-opentelemetry-autoconfigure/src/main/java/com/azure/monitor/opentelemetry/autoconfigure/implementation/quickpulse/filtering/TelemetryColumns.java
new file mode 100644
index 0000000000000..6a31bebaec053
--- /dev/null
+++ b/sdk/monitor/azure-monitor-opentelemetry-autoconfigure/src/main/java/com/azure/monitor/opentelemetry/autoconfigure/implementation/quickpulse/filtering/TelemetryColumns.java
@@ -0,0 +1,16 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package com.azure.monitor.opentelemetry.autoconfigure.implementation.quickpulse.filtering;
+
+import java.util.List;
+import java.util.Map;
+
+public interface TelemetryColumns {
+ T getFieldValue(String fieldName, Class type);
+
+ List getAllFieldValuesAsString();
+
+ Map getCustomDimensions();
+
+}
diff --git a/sdk/monitor/azure-monitor-opentelemetry-autoconfigure/src/main/java/com/azure/monitor/opentelemetry/autoconfigure/implementation/quickpulse/filtering/TraceDataColumns.java b/sdk/monitor/azure-monitor-opentelemetry-autoconfigure/src/main/java/com/azure/monitor/opentelemetry/autoconfigure/implementation/quickpulse/filtering/TraceDataColumns.java
new file mode 100644
index 0000000000000..ed07aa577e6fc
--- /dev/null
+++ b/sdk/monitor/azure-monitor-opentelemetry-autoconfigure/src/main/java/com/azure/monitor/opentelemetry/autoconfigure/implementation/quickpulse/filtering/TraceDataColumns.java
@@ -0,0 +1,44 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package com.azure.monitor.opentelemetry.autoconfigure.implementation.quickpulse.filtering;
+
+import com.azure.monitor.opentelemetry.autoconfigure.implementation.models.MessageData;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class TraceDataColumns implements TelemetryColumns {
+ private final Map mapping = new HashMap<>();
+
+ private final CustomDimensions customDims;
+
+ public TraceDataColumns(MessageData traceData) {
+ customDims = new CustomDimensions();
+ customDims.setCustomDimensions(traceData.getProperties(), traceData.getMeasurements());
+ mapping.put(KnownTraceColumns.MESSAGE, traceData.getMessage());
+ }
+
+ // to be used in tests only
+ public TraceDataColumns(String message, Map dims, Map measurements) {
+ customDims = new CustomDimensions();
+ customDims.setCustomDimensions(dims, measurements);
+ mapping.put(KnownTraceColumns.MESSAGE, message);
+ }
+
+ public Map getCustomDimensions() {
+ return this.customDims.getCustomDimensions();
+ }
+
+ public T getFieldValue(String fieldName, Class type) {
+ return type.cast(mapping.get(fieldName));
+ }
+
+ public List getAllFieldValuesAsString() {
+ List result = new ArrayList<>();
+ result.add((String) mapping.get(KnownTraceColumns.MESSAGE));
+ return result;
+ }
+}
diff --git a/sdk/monitor/azure-monitor-opentelemetry-autoconfigure/src/main/java/com/azure/monitor/opentelemetry/autoconfigure/implementation/utils/FormattedDuration.java b/sdk/monitor/azure-monitor-opentelemetry-autoconfigure/src/main/java/com/azure/monitor/opentelemetry/autoconfigure/implementation/utils/FormattedDuration.java
index 41aa192483284..8d9e7a1bd604d 100644
--- a/sdk/monitor/azure-monitor-opentelemetry-autoconfigure/src/main/java/com/azure/monitor/opentelemetry/autoconfigure/implementation/utils/FormattedDuration.java
+++ b/sdk/monitor/azure-monitor-opentelemetry-autoconfigure/src/main/java/com/azure/monitor/opentelemetry/autoconfigure/implementation/utils/FormattedDuration.java
@@ -44,6 +44,33 @@ public static String fromNanos(long durationNanos) {
return sb.toString();
}
+ // Returns duration in microseconds
+ public static long getDurationFromTelemetryItemDurationString(String duration) {
+ // duration in format: DD.HH:MM:SS.MMMMMM. Must be less than 1000 days.
+ try {
+ String[] parts = duration.split("\\.");
+ int days = 0;
+ String hms;
+ long microseconds;
+ if (parts.length == 3) {
+ days = Integer.parseInt(parts[0]);
+ hms = parts[1];
+ microseconds = Integer.parseInt(parts[2]);
+ } else { //length 2
+ hms = parts[0];
+ microseconds = Integer.parseInt(parts[1]);
+ }
+ String[] hmsParts = hms.split(":");
+ int hours = Integer.parseInt(hmsParts[0]);
+ int minutes = Integer.parseInt(hmsParts[1]);
+ int seconds = Integer.parseInt(hmsParts[2]);
+ return (days * 24L * 60L * 60L * 1000000L) + (hours * 60L * 60L * 1000000L) + (minutes * 60L * 1000000L)
+ + (seconds * 1000000L) + microseconds;
+ } catch (NumberFormatException e) {
+ return -1;
+ }
+ }
+
private static void appendDaysHoursMinutesSeconds(StringBuilder sb, long days, long hours, long minutes,
long seconds) {
if (days > 0) {
diff --git a/sdk/monitor/azure-monitor-opentelemetry-autoconfigure/src/test/java/com/azure/monitor/opentelemetry/autoconfigure/implementation/quickpulse/filteringTest/FilterTest.java b/sdk/monitor/azure-monitor-opentelemetry-autoconfigure/src/test/java/com/azure/monitor/opentelemetry/autoconfigure/implementation/quickpulse/filteringTest/FilterTest.java
new file mode 100644
index 0000000000000..5175f59b7c25c
--- /dev/null
+++ b/sdk/monitor/azure-monitor-opentelemetry-autoconfigure/src/test/java/com/azure/monitor/opentelemetry/autoconfigure/implementation/quickpulse/filteringTest/FilterTest.java
@@ -0,0 +1,485 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+package com.azure.monitor.opentelemetry.autoconfigure.implementation.quickpulse.filteringTest;
+
+import com.azure.monitor.opentelemetry.autoconfigure.implementation.models.MessageData;
+import com.azure.monitor.opentelemetry.autoconfigure.implementation.models.TelemetryExceptionData;
+import com.azure.monitor.opentelemetry.autoconfigure.implementation.models.TelemetryExceptionDetails;
+import com.azure.monitor.opentelemetry.autoconfigure.implementation.models.TelemetryItem;
+import com.azure.monitor.opentelemetry.autoconfigure.implementation.models.MonitorDomain;
+import com.azure.monitor.opentelemetry.autoconfigure.implementation.models.RequestData;
+import com.azure.monitor.opentelemetry.autoconfigure.implementation.models.RemoteDependencyData;
+import com.azure.monitor.opentelemetry.autoconfigure.implementation.quickpulse.QuickPulseTestBase;
+import com.azure.monitor.opentelemetry.autoconfigure.implementation.quickpulse.filtering.*;
+import com.azure.monitor.opentelemetry.autoconfigure.implementation.quickpulse.swagger.models.AggregationType;
+import com.azure.monitor.opentelemetry.autoconfigure.implementation.quickpulse.swagger.models.DerivedMetricInfo;
+import com.azure.monitor.opentelemetry.autoconfigure.implementation.quickpulse.swagger.models.FilterInfo;
+import com.azure.monitor.opentelemetry.autoconfigure.implementation.quickpulse.swagger.models.PredicateType;
+import com.azure.monitor.opentelemetry.autoconfigure.implementation.quickpulse.swagger.models.FilterConjunctionGroupInfo;
+
+import io.vertx.core.cli.annotations.Description;
+import org.junit.jupiter.api.Test;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Map;
+import java.util.HashMap;
+
+import static java.util.Arrays.asList;
+import static org.junit.jupiter.api.Assertions.*;
+
+class FilterTest {
+
+ private FilterInfo createFilterInfoWithParams(String fieldName, PredicateType predicate, String comparand) {
+ FilterInfo result = new FilterInfo();
+ result.setFieldName(fieldName);
+ result.setPredicate(predicate);
+ result.setComparand(comparand);
+ return result;
+ }
+
+ private DerivedMetricInfo createDerivedMetricInfo(String id, String telemetryType, AggregationType agg,
+ AggregationType backendAgg, String projection, List filterGroups) {
+ DerivedMetricInfo result = new DerivedMetricInfo();
+ result.setId(id);
+ result.setTelemetryType(telemetryType);
+ result.setAggregation(agg);
+ result.setBackEndAggregation(backendAgg);
+ result.setProjection(projection);
+ result.setFilterGroups(filterGroups);
+ return result;
+ }
+
+ private List createListWithOneFilterConjunctionGroupAndOneFilter(FilterInfo filter) {
+ List result = new ArrayList<>();
+ FilterConjunctionGroupInfo group = new FilterConjunctionGroupInfo();
+ List filters = new ArrayList<>();
+ filters.add(filter);
+ group.setFilters(filters);
+ result.add(group);
+ return result;
+ }
+
+ @Test
+ @Description("This tests if the any field (*) filter can filter telemetry correctly, with various combos of predicates & column types")
+ void testAnyFieldFilter() {
+ FilterInfo anyFieldContainsHi = createFilterInfoWithParams(Filter.ANY_FIELD, PredicateType.CONTAINS, "hi");
+ FilterInfo anyFieldNotContains
+ = createFilterInfoWithParams(Filter.ANY_FIELD, PredicateType.DOES_NOT_CONTAIN, "hi");
+ FilterInfo anyFieldContainsCool = createFilterInfoWithParams(Filter.ANY_FIELD, PredicateType.CONTAINS, "cool");
+ FilterInfo anyFieldForNumeric = createFilterInfoWithParams(Filter.ANY_FIELD, PredicateType.CONTAINS, "200");
+ FilterInfo anyFieldForBoolean = createFilterInfoWithParams(Filter.ANY_FIELD, PredicateType.CONTAINS, "true");
+
+ RequestDataColumns request1
+ = new RequestDataColumns("https://test.com/hiThere", 200, 200, true, "GET /hiThere", null, null);
+ Map customDims = new HashMap<>();
+ customDims.put("property", "cool");
+ RequestDataColumns request2
+ = new RequestDataColumns("https://test.com/bye", 200, 200, true, "GET /bye", customDims, null);
+
+ List filterGroups
+ = createListWithOneFilterConjunctionGroupAndOneFilter(anyFieldContainsHi);
+ DerivedMetricInfo derivedMetricInfo = createDerivedMetricInfo("random-id", "Request", AggregationType.SUM,
+ AggregationType.SUM, "Count()", filterGroups);
+
+ FilterConjunctionGroupInfo filterGroup = filterGroups.get(0);
+
+ // request contains "hi" in multiple fields & filter is contains hi
+ assertTrue(Filter.checkFilterConjunctionGroup(filterGroup, request1));
+ assertTrue(Filter.checkMetricFilters(derivedMetricInfo, request1));
+
+ // request does not contain "hi" in any field & filter is contains hi
+ assertFalse(Filter.checkFilterConjunctionGroup(filterGroup, request2));
+ assertFalse(Filter.checkMetricFilters(derivedMetricInfo, request2));
+
+ // request does not contain "hi" in any field & filter is does not contain hi
+ filterGroup.setFilters(asList(anyFieldNotContains));
+ assertTrue(Filter.checkFilterConjunctionGroup(filterGroup, request2));
+ assertTrue(Filter.checkMetricFilters(derivedMetricInfo, request2));
+
+ // request contains "cool" in custom dimensions & filter is contains cool
+ filterGroup.setFilters(asList(anyFieldContainsCool));
+ assertTrue(Filter.checkFilterConjunctionGroup(filterGroup, request2));
+ assertTrue(Filter.checkMetricFilters(derivedMetricInfo, request2));
+
+ // request contains 200 in duration & filter is contains "200".
+ // fields are expected to be treated as string
+ filterGroup.setFilters(asList(anyFieldForNumeric));
+ assertTrue(Filter.checkFilterConjunctionGroup(filterGroup, request1));
+ assertTrue(Filter.checkMetricFilters(derivedMetricInfo, request1));
+
+ // request contains true in Success & filter is contains "true".
+ // fields are expected to be treated as string
+ filterGroup.setFilters(asList(anyFieldForBoolean));
+ assertTrue(Filter.checkFilterConjunctionGroup(filterGroup, request1));
+ assertTrue(Filter.checkMetricFilters(derivedMetricInfo, request1));
+
+ }
+
+ @Test
+ @Description("This tests if the custom dimension filter works correctly, with various predicates")
+ void testCustomDimensionFilter() {
+ FilterInfo customDimFilter
+ = createFilterInfoWithParams(Filter.CUSTOM_DIM_FIELDNAME_PREFIX + "hi", PredicateType.EQUAL, "hi");
+ List filterGroups
+ = createListWithOneFilterConjunctionGroupAndOneFilter(customDimFilter);
+ DerivedMetricInfo derivedMetricInfo = createDerivedMetricInfo("random-id", "Request", AggregationType.SUM,
+ AggregationType.SUM, "Count()", filterGroups);
+
+ Map customDims1 = new HashMap<>();
+ customDims1.put("property", "hi");
+
+ Map customDimsHiBye = new HashMap<>();
+ customDimsHiBye.put("hi", "bye");
+
+ Map customDimsHi = new HashMap<>();
+ customDimsHi.put("hi", "hi");
+
+ Map customDimsHiThere = new HashMap<>();
+ customDimsHiThere.put("hi", "hi there");
+
+ RequestDataColumns request1
+ = new RequestDataColumns("https://test.com/hiThere", 200, 200, true, "GET /hiThere", customDims1, null);
+ RequestDataColumns requestHiBye
+ = new RequestDataColumns("https://test.com/hiThere", 200, 200, true, "GET /hiThere", customDimsHiBye, null);
+ RequestDataColumns requestHi
+ = new RequestDataColumns("https://test.com/hiThere", 200, 200, true, "GET /hiThere", customDimsHi, null);
+ RequestDataColumns requestHiThere = new RequestDataColumns("https://test.com/hiThere", 200, 200, true,
+ "GET /hiThere", customDimsHiThere, null);
+
+ FilterConjunctionGroupInfo filterGroup = filterGroups.get(0);
+
+ // the asked for field is not in the custom dimensions so return false
+ assertFalse(Filter.checkFilterConjunctionGroup(filterGroup, request1));
+ assertFalse(Filter.checkMetricFilters(derivedMetricInfo, request1));
+
+ // the asked for field is in the custom dimensions but value does not match
+ assertFalse(Filter.checkFilterConjunctionGroup(filterGroup, requestHiBye));
+ assertFalse(Filter.checkMetricFilters(derivedMetricInfo, requestHiBye));
+
+ // the asked for field is in the custom dimensions and value matches
+ assertTrue(Filter.checkFilterConjunctionGroup(filterGroup, requestHi));
+ assertTrue(Filter.checkMetricFilters(derivedMetricInfo, requestHi));
+
+ // testing not equal predicate. The CustomDimensions.hi value != hi so return true.
+ customDimFilter.setPredicate(PredicateType.NOT_EQUAL);
+ assertTrue(Filter.checkFilterConjunctionGroup(filterGroup, requestHiBye));
+ assertTrue(Filter.checkMetricFilters(derivedMetricInfo, requestHiBye));
+
+ // testing does not contain predicate. The CustomDimensions.hi value does not contain hi so return true.
+ customDimFilter.setPredicate(PredicateType.DOES_NOT_CONTAIN);
+ assertTrue(Filter.checkFilterConjunctionGroup(filterGroup, requestHiBye));
+ assertTrue(Filter.checkMetricFilters(derivedMetricInfo, requestHiBye));
+
+ // testing contains predicate. The CustomDimensions.hi value contains hi so return true.
+ customDimFilter.setPredicate(PredicateType.CONTAINS);
+ assertTrue(Filter.checkFilterConjunctionGroup(filterGroup, requestHiThere));
+ assertTrue(Filter.checkMetricFilters(derivedMetricInfo, requestHiThere));
+ }
+
+ @Test
+ @Description("This tests if filters on known boolean columns (Success) work correctly, with various predicates and telemetry types")
+ void testBooleanFilter() {
+ FilterInfo booleanFilter = createFilterInfoWithParams(KnownRequestColumns.SUCCESS, PredicateType.EQUAL, "true");
+ List filterGroups
+ = createListWithOneFilterConjunctionGroupAndOneFilter(booleanFilter);
+ DerivedMetricInfo derivedMetricInfo = createDerivedMetricInfo("random-id", "Request", AggregationType.SUM,
+ AggregationType.SUM, "Count()", filterGroups);
+ RequestDataColumns requestTrue
+ = new RequestDataColumns("https://test.com/hiThere", 200, 200, true, "GET /hiThere", null, null);
+ RequestDataColumns requestFalse
+ = new RequestDataColumns("https://test.com/hiThere", 200, 0, false, "GET /hiThere", null, null);
+ DependencyDataColumns dependencyTrue = new DependencyDataColumns("test.com", 200, true, "GET /hiThere", 200,
+ "HTTP", "https://test.com/hiThere?x=y", null, null);
+ DependencyDataColumns dependencyFalse = new DependencyDataColumns("test.com", 200, false, "GET /hiThere", 0,
+ "HTTP", "https://test.com/hiThere?x=y", null, null);
+ FilterConjunctionGroupInfo filterGroup = filterGroups.get(0);
+
+ // Request Success filter matches
+ assertTrue(Filter.checkFilterConjunctionGroup(filterGroup, requestTrue));
+ assertTrue(Filter.checkMetricFilters(derivedMetricInfo, requestTrue));
+
+ // Request Success filter does not match
+ assertFalse(Filter.checkFilterConjunctionGroup(filterGroup, requestFalse));
+ assertFalse(Filter.checkMetricFilters(derivedMetricInfo, requestFalse));
+
+ // Request Success filter matches for != predicate
+ booleanFilter.setPredicate(PredicateType.NOT_EQUAL);
+ assertTrue(Filter.checkFilterConjunctionGroup(filterGroup, requestFalse));
+ assertTrue(Filter.checkMetricFilters(derivedMetricInfo, requestFalse));
+
+ // Dependency Success filter matches
+ derivedMetricInfo.setTelemetryType("Dependency");
+ booleanFilter.setPredicate(PredicateType.EQUAL);
+ assertTrue(Filter.checkFilterConjunctionGroup(filterGroup, dependencyTrue));
+ assertTrue(Filter.checkMetricFilters(derivedMetricInfo, dependencyTrue));
+
+ // Dependency Success filter does not match
+ assertFalse(Filter.checkFilterConjunctionGroup(filterGroup, dependencyFalse));
+ assertFalse(Filter.checkMetricFilters(derivedMetricInfo, dependencyFalse));
+
+ // Dependency Success filter matches for != predicate
+ booleanFilter.setPredicate(PredicateType.NOT_EQUAL);
+ assertTrue(Filter.checkFilterConjunctionGroup(filterGroup, dependencyFalse));
+ assertTrue(Filter.checkMetricFilters(derivedMetricInfo, dependencyFalse));
+ }
+
+ @Test
+ @Description("This tests if filtering works on known numeric columns, with various predicates and telemetry types")
+ void testNumericFilters() {
+ FilterInfo numericFilter
+ = createFilterInfoWithParams(KnownRequestColumns.RESPONSE_CODE, PredicateType.EQUAL, "200");
+ List filterGroups
+ = createListWithOneFilterConjunctionGroupAndOneFilter(numericFilter);
+ DerivedMetricInfo derivedMetricInfo = createDerivedMetricInfo("random-id", "Request", AggregationType.SUM,
+ AggregationType.SUM, "Count()", filterGroups);
+ RequestDataColumns requestSuccessful
+ = new RequestDataColumns("https://test.com/hiThere", 1234567890000L, 200, true, "GET /hiThere", null, null);
+ RequestDataColumns requestFail = new RequestDataColumns("https://test.com/hiThere", 1234567890000L, 404, false,
+ "GET /hiThere", null, null);
+ RequestDataColumns requestShortDuration
+ = new RequestDataColumns("https://test.com/hiThere", 400, 200, true, "GET /hiThere", null, null);
+ DependencyDataColumns dependencySuccessful = new DependencyDataColumns("test.com", 1234567890000L, true,
+ "GET /hiThere", 200, "HTTP", "https://test.com/hiThere?x=y", null, null);
+ DependencyDataColumns dependencyFail = new DependencyDataColumns("test.com", 1234567890000L, false,
+ "GET /hiThere", 0, "HTTP", "https://test.com/hiThere?x=y", null, null);
+ DependencyDataColumns dependencyShortDuration = new DependencyDataColumns("test.com", 400, true, "GET /hiThere",
+ 200, "HTTP", "https://test.com/hiThere?x=y", null, null);
+ FilterConjunctionGroupInfo filterGroup = filterGroups.get(0);
+
+ // Request ResponseCode filter matches
+ assertTrue(Filter.checkFilterConjunctionGroup(filterGroup, requestSuccessful));
+ assertTrue(Filter.checkMetricFilters(derivedMetricInfo, requestSuccessful));
+
+ // Request ResponseCode filter does not match
+ assertFalse(Filter.checkFilterConjunctionGroup(filterGroup, requestFail));
+ assertFalse(Filter.checkMetricFilters(derivedMetricInfo, requestFail));
+
+ // Dependency ResultCode filter matches
+ derivedMetricInfo.setTelemetryType("Dependency");
+ numericFilter.setFieldName(KnownDependencyColumns.RESULT_CODE);
+ assertTrue(Filter.checkFilterConjunctionGroup(filterGroup, dependencySuccessful));
+ assertTrue(Filter.checkMetricFilters(derivedMetricInfo, dependencySuccessful));
+
+ // Dependency ResultCode filter does not match
+ assertFalse(Filter.checkFilterConjunctionGroup(filterGroup, dependencyFail));
+ assertFalse(Filter.checkMetricFilters(derivedMetricInfo, dependencyFail));
+
+ // Dependency Duration filter matches
+ numericFilter.setFieldName(KnownDependencyColumns.DURATION);
+ // 14 days, 6 hrs, 56 miutes, 7.89 seconds (1234567890 ms is the matching value for what the user would put in filtering UI)
+ // the UI sends the following string down to SDK
+ numericFilter.setComparand("14.6:56:7.89");
+ assertTrue(Filter.checkFilterConjunctionGroup(filterGroup, dependencyFail));
+ assertTrue(Filter.checkMetricFilters(derivedMetricInfo, dependencyFail));
+
+ // Dependency duration filter does not match
+ assertFalse(Filter.checkFilterConjunctionGroup(filterGroup, dependencyShortDuration));
+ assertFalse(Filter.checkMetricFilters(derivedMetricInfo, dependencyShortDuration));
+
+ // Request Duration filter matches
+ numericFilter.setFieldName(KnownRequestColumns.DURATION);
+ derivedMetricInfo.setTelemetryType("Request");
+ assertTrue(Filter.checkFilterConjunctionGroup(filterGroup, requestSuccessful));
+ assertTrue(Filter.checkMetricFilters(derivedMetricInfo, requestSuccessful));
+
+ // Dependency duration filter does not match
+ assertFalse(Filter.checkFilterConjunctionGroup(filterGroup, requestShortDuration));
+ assertFalse(Filter.checkMetricFilters(derivedMetricInfo, requestShortDuration));
+
+ // != predicate
+ numericFilter.setPredicate(PredicateType.NOT_EQUAL);
+ assertTrue(Filter.checkFilterConjunctionGroup(filterGroup, requestShortDuration));
+ assertTrue(Filter.checkMetricFilters(derivedMetricInfo, requestShortDuration));
+
+ // < predicate
+ numericFilter.setPredicate(PredicateType.LESS_THAN);
+ assertTrue(Filter.checkFilterConjunctionGroup(filterGroup, requestShortDuration));
+ assertTrue(Filter.checkMetricFilters(derivedMetricInfo, requestShortDuration));
+
+ // <= predicate
+ numericFilter.setPredicate(PredicateType.LESS_THAN_OR_EQUAL);
+ assertTrue(Filter.checkFilterConjunctionGroup(filterGroup, requestShortDuration));
+ assertTrue(Filter.checkMetricFilters(derivedMetricInfo, requestShortDuration));
+
+ // > predicate
+ numericFilter.setPredicate(PredicateType.GREATER_THAN);
+ assertFalse(Filter.checkFilterConjunctionGroup(filterGroup, requestShortDuration));
+ assertFalse(Filter.checkMetricFilters(derivedMetricInfo, requestShortDuration));
+
+ // >= predicate
+ numericFilter.setPredicate(PredicateType.GREATER_THAN_OR_EQUAL);
+ assertFalse(Filter.checkFilterConjunctionGroup(filterGroup, requestShortDuration));
+ assertFalse(Filter.checkMetricFilters(derivedMetricInfo, requestShortDuration));
+ }
+
+ @Test
+ @Description("This tests if filtering works on a known string column from each telemetry type, with various predicates")
+ void testStringFilters() {
+ FilterInfo stringFilter = createFilterInfoWithParams(KnownRequestColumns.URL, PredicateType.CONTAINS, "hi");
+ List filterGroups
+ = createListWithOneFilterConjunctionGroupAndOneFilter(stringFilter);
+ DerivedMetricInfo derivedMetricInfo = createDerivedMetricInfo("random-id", "Request", AggregationType.SUM,
+ AggregationType.SUM, "Count()", filterGroups);
+ RequestDataColumns requestHi
+ = new RequestDataColumns("https://test.com/hiThere", 200, 200, true, "GET /hiThere", null, null);
+ RequestDataColumns requestBye
+ = new RequestDataColumns("https://test.com/bye", 200, 200, true, "GET /bye", null, null);
+ DependencyDataColumns dependencyHi = new DependencyDataColumns("test.com", 200, true, "GET /hiThere", 200,
+ "HTTP", "https://test.com/hiThere?x=y", null, null);
+ DependencyDataColumns dependencyBye = new DependencyDataColumns("test.com", 200, true, "GET /bye", 200, "HTTP",
+ "https://test.com/bye", null, null);
+ TraceDataColumns traceHi = new TraceDataColumns("hi there", null, null);
+ TraceDataColumns traceBye = new TraceDataColumns("bye", null, null);
+ ExceptionDataColumns exceptionHi = new ExceptionDataColumns("Exception Message hi", "Stack Trace", null, null);
+ ExceptionDataColumns exception = new ExceptionDataColumns("Exception Message", "Stack Trace", null, null);
+ FilterConjunctionGroupInfo filterGroup = filterGroups.get(0);
+
+ // Request Url filter matches
+ assertTrue(Filter.checkFilterConjunctionGroup(filterGroup, requestHi));
+ assertTrue(Filter.checkMetricFilters(derivedMetricInfo, requestHi));
+
+ // Request Url filter does not match
+ assertFalse(Filter.checkFilterConjunctionGroup(filterGroup, requestBye));
+ assertFalse(Filter.checkMetricFilters(derivedMetricInfo, requestBye));
+
+ // Dependency Data filter matches
+ derivedMetricInfo.setTelemetryType("Dependency");
+ stringFilter.setFieldName(KnownDependencyColumns.DATA);
+ assertTrue(Filter.checkFilterConjunctionGroup(filterGroup, dependencyHi));
+ assertTrue(Filter.checkMetricFilters(derivedMetricInfo, dependencyHi));
+
+ // Dependency Data filter does not match
+ assertFalse(Filter.checkFilterConjunctionGroup(filterGroup, dependencyBye));
+ assertFalse(Filter.checkMetricFilters(derivedMetricInfo, dependencyBye));
+
+ // Trace Message filter matches
+ derivedMetricInfo.setTelemetryType("Trace");
+ stringFilter.setFieldName(KnownTraceColumns.MESSAGE);
+ assertTrue(Filter.checkFilterConjunctionGroup(filterGroup, traceHi));
+ assertTrue(Filter.checkMetricFilters(derivedMetricInfo, traceHi));
+
+ // Trace Message filter does not match
+ assertFalse(Filter.checkFilterConjunctionGroup(filterGroup, traceBye));
+ assertFalse(Filter.checkMetricFilters(derivedMetricInfo, traceBye));
+
+ // Exception.Message filter matches.
+ derivedMetricInfo.setTelemetryType("Exception");
+ stringFilter.setFieldName(KnownExceptionColumns.MESSAGE);
+ assertTrue(Filter.checkFilterConjunctionGroup(filterGroup, exceptionHi));
+ assertTrue(Filter.checkMetricFilters(derivedMetricInfo, exceptionHi));
+
+ // Exception Message filter does not match
+ assertFalse(Filter.checkFilterConjunctionGroup(filterGroup, exception));
+ assertFalse(Filter.checkMetricFilters(derivedMetricInfo, exception));
+
+ // != predicate
+ stringFilter.setPredicate(PredicateType.NOT_EQUAL);
+ assertTrue(Filter.checkFilterConjunctionGroup(filterGroup, exception));
+ assertTrue(Filter.checkMetricFilters(derivedMetricInfo, exception));
+
+ // not contains
+ stringFilter.setPredicate(PredicateType.DOES_NOT_CONTAIN);
+ assertTrue(Filter.checkFilterConjunctionGroup(filterGroup, exception));
+ assertTrue(Filter.checkMetricFilters(derivedMetricInfo, exception));
+
+ // equal
+ stringFilter.setPredicate(PredicateType.EQUAL);
+ assertFalse(Filter.checkFilterConjunctionGroup(filterGroup, exception));
+ assertFalse(Filter.checkMetricFilters(derivedMetricInfo, exception));
+ }
+
+ @Test
+ @Description("If a FilterConjunctionGroupInfo has an empty list of filters, telemetry should match")
+ void testEmptyFilterConjunctionGroupInfo() {
+ // create empty filter list
+ FilterConjunctionGroupInfo filterGroup = new FilterConjunctionGroupInfo();
+ filterGroup.setFilters(new ArrayList());
+ List filterGroups = asList(filterGroup);
+
+ DerivedMetricInfo derivedMetricInfo = createDerivedMetricInfo("random-id", "Request", AggregationType.SUM,
+ AggregationType.SUM, "Count()", filterGroups);
+ RequestDataColumns request
+ = new RequestDataColumns("https://test.com/hiThere", 200, 200, true, "GET /hiThere", null, null);
+
+ assertTrue(Filter.checkFilterConjunctionGroup(filterGroup, request));
+ assertTrue(Filter.checkMetricFilters(derivedMetricInfo, request));
+ }
+
+ @Test
+ @Description("If there are multiple filters in a FilterConjunctionGroupInfo, then telemetry should only match if all conditions satisfied")
+ void testMultipleFiltersInGroup() {
+ FilterInfo filter1 = createFilterInfoWithParams(KnownRequestColumns.URL, PredicateType.CONTAINS, "hi");
+ FilterInfo filter2 = createFilterInfoWithParams(KnownRequestColumns.RESPONSE_CODE, PredicateType.EQUAL, "200");
+ FilterConjunctionGroupInfo filterGroup = new FilterConjunctionGroupInfo();
+ filterGroup.setFilters(asList(filter1, filter2));
+ List filterGroups = asList(filterGroup);
+
+ DerivedMetricInfo derivedMetricInfo = createDerivedMetricInfo("random-id", "Request", AggregationType.SUM,
+ AggregationType.SUM, "Count()", filterGroups);
+ RequestDataColumns requestHi
+ = new RequestDataColumns("https://test.com/hiThere", 200, 200, true, "GET /hiThere", null, null);
+ RequestDataColumns requestBye
+ = new RequestDataColumns("https://test.com/bye", 200, 200, true, "GET /bye", null, null);
+
+ // matches both filters
+ assertTrue(Filter.checkFilterConjunctionGroup(filterGroup, requestHi));
+ assertTrue(Filter.checkMetricFilters(derivedMetricInfo, requestHi));
+
+ // only one filter matches, the entire conjunction group should return false
+ assertFalse(Filter.checkFilterConjunctionGroup(filterGroup, requestBye));
+ assertFalse(Filter.checkMetricFilters(derivedMetricInfo, requestBye));
+ }
+
+ @Test
+ @Description("Test the constructors for child classes of TelemetryColumns")
+ void testConstructors() {
+ TelemetryItem requestItem
+ = QuickPulseTestBase.createRequestTelemetry("GET /hiThere", new Date(), 1234567890L, "200", true);
+ TelemetryItem dependencyItem = QuickPulseTestBase.createRemoteDependencyTelemetry("GET /hiThere",
+ "https://test.com/hiThere?x=y", 400, true);
+
+ TelemetryExceptionData exceptionItem = new TelemetryExceptionData();
+ TelemetryExceptionDetails details = new TelemetryExceptionDetails();
+ details.setMessage("A message");
+ details.setStack("A stack trace");
+ exceptionItem.setExceptions(asList(details));
+
+ MessageData traceItem = new MessageData();
+ traceItem.setMessage("A message");
+
+ MonitorDomain requestData = requestItem.getData().getBaseData();
+ RequestDataColumns requestDataColumns = new RequestDataColumns((RequestData) requestData);
+
+ MonitorDomain dependencyData = dependencyItem.getData().getBaseData();
+ ((RemoteDependencyData) dependencyData).setType("HTTP");
+ ((RemoteDependencyData) dependencyData).setTarget("test.com");
+ ((RemoteDependencyData) dependencyData).setResultCode("200");
+ DependencyDataColumns dependencyDataColumns = new DependencyDataColumns((RemoteDependencyData) dependencyData);
+
+ ExceptionDataColumns exceptionData = new ExceptionDataColumns(exceptionItem);
+ TraceDataColumns traceDataColumns = new TraceDataColumns(traceItem);
+
+ assertTrue(requestDataColumns.getFieldValue(KnownRequestColumns.SUCCESS, Boolean.class));
+ assertEquals(requestDataColumns.getFieldValue(KnownRequestColumns.DURATION, Long.class), 1234567890000L);
+ assertEquals(requestDataColumns.getFieldValue(KnownRequestColumns.RESPONSE_CODE, Integer.class), 200);
+ assertEquals(requestDataColumns.getFieldValue(KnownRequestColumns.NAME, String.class), "GET /hiThere");
+ assertEquals(requestDataColumns.getFieldValue(KnownRequestColumns.URL, String.class), "foo");
+
+ assertTrue(dependencyDataColumns.getFieldValue(KnownRequestColumns.SUCCESS, Boolean.class));
+ assertEquals(dependencyDataColumns.getFieldValue(KnownDependencyColumns.DATA, String.class),
+ "https://test.com/hiThere?x=y");
+ assertEquals(dependencyDataColumns.getFieldValue(KnownRequestColumns.NAME, String.class), "GET /hiThere");
+ assertEquals(dependencyDataColumns.getFieldValue(KnownDependencyColumns.TYPE, String.class), "HTTP");
+ assertEquals(dependencyDataColumns.getFieldValue(KnownRequestColumns.DURATION, Long.class), 400000L);
+ assertEquals(dependencyDataColumns.getFieldValue(KnownDependencyColumns.RESULT_CODE, Integer.class), 200);
+ assertEquals(dependencyDataColumns.getFieldValue(KnownDependencyColumns.TARGET, String.class), "test.com");
+
+ assertEquals(exceptionData.getFieldValue(KnownExceptionColumns.MESSAGE, String.class), "A message");
+ assertEquals(exceptionData.getFieldValue(KnownExceptionColumns.STACK, String.class), "A stack trace");
+
+ assertEquals(traceDataColumns.getFieldValue(KnownTraceColumns.MESSAGE, String.class), "A message");
+ }
+
+}