diff --git a/components/inspectit-ocelot-eum-server/src/main/java/rocks/inspectit/oce/eum/server/configuration/model/BeaconRequirement.java b/components/inspectit-ocelot-eum-server/src/main/java/rocks/inspectit/oce/eum/server/configuration/model/BeaconRequirement.java index 7e512b3d9b..920fdd68ef 100644 --- a/components/inspectit-ocelot-eum-server/src/main/java/rocks/inspectit/oce/eum/server/configuration/model/BeaconRequirement.java +++ b/components/inspectit-ocelot-eum-server/src/main/java/rocks/inspectit/oce/eum/server/configuration/model/BeaconRequirement.java @@ -3,11 +3,13 @@ import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; import rocks.inspectit.oce.eum.server.beacon.Beacon; -import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.AssertTrue; import javax.validation.constraints.NotNull; import java.util.Collection; +import java.util.List; /** * Beacon requirements. Used for excluding beacons under certain circumstances. @@ -22,17 +24,14 @@ public class BeaconRequirement { * * @param beacon the beacon to validate * @param requirements list of requirements which have to be fulfilled + * * @return true in case the beacon fulfills the requirements otherwise false is returned */ public static boolean validate(Beacon beacon, Collection requirements) { if (CollectionUtils.isEmpty(requirements)) { return true; } - - boolean notFulfilled = requirements.stream() - .anyMatch(requirement -> !requirement.validate(beacon)); - - return !notFulfilled; + return requirements.stream().allMatch(requirement -> requirement.validate(beacon)); } /** @@ -48,14 +47,13 @@ public enum RequirementType { /** * The target field must not exist. */ - NOT_EXISTS - } + NOT_EXISTS, - /** - * The field which is targeted. - */ - @NotEmpty - private String field; + /** + * Checks if the beacon has an initiator type listed in the {@link #initiators} array. + */ + HAS_INITIATOR, + } /** * the type of requirement. @@ -63,10 +61,22 @@ public enum RequirementType { @NotNull private RequirementType requirement; + /** + * The list of allowed initiators for the {@link RequirementType#HAS_INITIATOR} requirement. + */ + private List initiators; + + /** + * The field which is targeted. + * Used for the {@link RequirementType#EXISTS} and {@link RequirementType#NOT_EXISTS} requirement types. + */ + private String field; + /** * Checks whether the given beacon fulfills this requirement. * * @param beacon the beacon to check + * * @return true in case the beacon fulfills the requirement otherwise false is returned */ public boolean validate(Beacon beacon) { @@ -75,6 +85,22 @@ public boolean validate(Beacon beacon) { return beacon.contains(field); case NOT_EXISTS: return !beacon.contains(field); + case HAS_INITIATOR: + return initiators.stream().anyMatch(initiatorType -> initiatorType.hasInitiator(beacon)); + default: + log.error("Requirement of type {} is not supported.", requirement); + return false; + } + } + + @AssertTrue + public boolean isValid() { + switch (requirement) { + case EXISTS: + case NOT_EXISTS: + return !StringUtils.isEmpty(field); + case HAS_INITIATOR: + return !CollectionUtils.isEmpty(initiators); default: log.error("Requirement of type {} is not supported.", requirement); return false; diff --git a/components/inspectit-ocelot-eum-server/src/main/java/rocks/inspectit/oce/eum/server/configuration/model/InitiatorType.java b/components/inspectit-ocelot-eum-server/src/main/java/rocks/inspectit/oce/eum/server/configuration/model/InitiatorType.java new file mode 100644 index 0000000000..011299d556 --- /dev/null +++ b/components/inspectit-ocelot-eum-server/src/main/java/rocks/inspectit/oce/eum/server/configuration/model/InitiatorType.java @@ -0,0 +1,48 @@ +package rocks.inspectit.oce.eum.server.configuration.model; + +import rocks.inspectit.oce.eum.server.beacon.Beacon; + +/** + * Types of initiators for beacons. + * These correspond to values for the http.initiator fields of beacons. + */ +public enum InitiatorType { + DOCUMENT { + @Override + boolean isEqualToBeaconHttpInitiator(String httpInitiatorValue) { + return httpInitiatorValue == null || "".equals(httpInitiatorValue); + } + }, + XHR { + @Override + boolean isEqualToBeaconHttpInitiator(String httpInitiatorValue) { + return "xhr".equalsIgnoreCase(httpInitiatorValue); + } + }, + SPA_SOFT { + @Override + boolean isEqualToBeaconHttpInitiator(String httpInitiatorValue) { + return "spa".equalsIgnoreCase(httpInitiatorValue); + } + }, + SPA_HARD { + @Override + boolean isEqualToBeaconHttpInitiator(String httpInitiatorValue) { + return "spa_hard".equalsIgnoreCase(httpInitiatorValue); + } + }; + + abstract boolean isEqualToBeaconHttpInitiator(String httpInitiatorValue); + + /** + * Checks if the given beacon has an initiator matching this {@link InitiatorType}. + * + * @param beacon the beacon to check + * + * @return true, if the initiator matches + */ + public boolean hasInitiator(Beacon beacon) { + return isEqualToBeaconHttpInitiator(beacon.get("http.initiator")); + } + +} diff --git a/components/inspectit-ocelot-eum-server/src/test/java/rocks/inspectit/oce/eum/server/configuration/model/BeaconRequirementTest.java b/components/inspectit-ocelot-eum-server/src/test/java/rocks/inspectit/oce/eum/server/configuration/model/BeaconRequirementTest.java index d6741523bf..32cfd69418 100644 --- a/components/inspectit-ocelot-eum-server/src/test/java/rocks/inspectit/oce/eum/server/configuration/model/BeaconRequirementTest.java +++ b/components/inspectit-ocelot-eum-server/src/test/java/rocks/inspectit/oce/eum/server/configuration/model/BeaconRequirementTest.java @@ -72,12 +72,11 @@ public void emptyList() { @Nested public class Validate { - private Beacon beacon = Beacon.of(Collections.singletonMap("field", "5")); - private BeaconRequirement requirement = new BeaconRequirement(); @Test - public void noFulfillment_notExists() { + public void noFulfillmentNotExists() { + Beacon beacon = Beacon.of(Collections.singletonMap("field", "5")); requirement.setField("field"); requirement.setRequirement(BeaconRequirement.RequirementType.NOT_EXISTS); @@ -87,7 +86,8 @@ public void noFulfillment_notExists() { } @Test - public void fulfillment_notExists() { + public void fulfillmentNotExists() { + Beacon beacon = Beacon.of(Collections.singletonMap("field", "5")); requirement.setField("another"); requirement.setRequirement(BeaconRequirement.RequirementType.NOT_EXISTS); @@ -97,7 +97,8 @@ public void fulfillment_notExists() { } @Test - public void noFulfillment_exists() { + public void noFulfillmentExists() { + Beacon beacon = Beacon.of(Collections.singletonMap("field", "5")); requirement.setField("another"); requirement.setRequirement(BeaconRequirement.RequirementType.EXISTS); @@ -107,7 +108,8 @@ public void noFulfillment_exists() { } @Test - public void fulfillment_exists() { + public void fulfillmentExists() { + Beacon beacon = Beacon.of(Collections.singletonMap("field", "5")); requirement.setField("field"); requirement.setRequirement(BeaconRequirement.RequirementType.EXISTS); @@ -115,5 +117,49 @@ public void fulfillment_exists() { assertThat(result).isTrue(); } + + @Test + public void wrongInitiator() { + Beacon beacon = Beacon.of(Collections.singletonMap("http.initiator", "spa")); + requirement.setInitiators(Arrays.asList(InitiatorType.SPA_HARD)); + requirement.setRequirement(BeaconRequirement.RequirementType.HAS_INITIATOR); + + boolean result = requirement.validate(beacon); + + assertThat(result).isFalse(); + } + + @Test + public void correctInitiator() { + Beacon beacon = Beacon.of(Collections.singletonMap("http.initiator", "xhr")); + requirement.setInitiators(Arrays.asList(InitiatorType.SPA_HARD, InitiatorType.XHR)); + requirement.setRequirement(BeaconRequirement.RequirementType.HAS_INITIATOR); + + boolean result = requirement.validate(beacon); + + assertThat(result).isTrue(); + } + + @Test + public void nullInitiator() { + Beacon beacon = Beacon.of(Collections.emptyMap()); + requirement.setInitiators(Arrays.asList(InitiatorType.SPA_HARD, InitiatorType.XHR)); + requirement.setRequirement(BeaconRequirement.RequirementType.HAS_INITIATOR); + + boolean result = requirement.validate(beacon); + + assertThat(result).isFalse(); + } + + @Test + public void documentInitiator() { + Beacon beacon = Beacon.of(Collections.emptyMap()); + requirement.setInitiators(Collections.singletonList(InitiatorType.DOCUMENT)); + requirement.setRequirement(BeaconRequirement.RequirementType.HAS_INITIATOR); + + boolean result = requirement.validate(beacon); + + assertThat(result).isTrue(); + } } } \ No newline at end of file diff --git a/inspectit-ocelot-documentation/docs/enduser-monitoring/eum-server-configuration.md b/inspectit-ocelot-documentation/docs/enduser-monitoring/eum-server-configuration.md index e81fdf068c..140f17e8b4 100644 --- a/inspectit-ocelot-documentation/docs/enduser-monitoring/eum-server-configuration.md +++ b/inspectit-ocelot-documentation/docs/enduser-monitoring/eum-server-configuration.md @@ -142,7 +142,15 @@ Using the operations above, complex calculations can be done, for example: The `beacon-requirements` field can be used to specify requirements which have to be fulfilled by the beacons in order to be evaluated by a certain metric. If any requirement does not fit a beacon, the beacon is ignored by the metric. -Beacon requirements consist of two attributes `field` and `requirement`. `field` specified the beacon's field which is validated using the requirement type specified in `NOT_EXISTS`. +The following requirements are available: + +| Type | Note | +| --- | --- | +| `EXISTS` | The targeted field must exist. | +| `NOT_EXISTS` | The targeted field must not exist. | +| `HAS_INITIATOR` | The beacon must have one of the specified initiators. | + +Firstly, you can specify that the metric expects a field to exist or not exist in the beacon: ```YAML ... @@ -152,13 +160,22 @@ Beacon requirements consist of two attributes `field` and `requirement`. `field` - field: rt.quit requirement: NOT_EXISTS ``` +In this example, `my-metric` will only be recorded if the field `rt.quit` does not exist in the beacon. +Alternatively you can use the `requirement` `EXISTS` to make sure the metric is only recorded if the given field is present. -The following requirement types are currently be supported: +Additionally you can specify that you only want to record the metric if the received beacon has a specific initiator: -| Type | Note | -| --- | --- | -| `EXISTS` | The targeted field must exist. | -| `NOT_EXISTS` | The targeted field must not exist. | +```YAML + ... + my-metric: + ... + beacon-requirements: + - initiators: [SPA_SOFT, SPA_HARD] + requirement: HAS_INITIATOR +``` + +The available initiators are `DOCUMENT` for the initial pageload beacons, `XHR` for Ajax-Beacons and `SPA_SOFT` and `SPA_HARD` for soft and hard SPA navigation beacons. +The metric will only be recorded for beacons whose `http.initiator` field matches any of the elements provided in the `initiators` list. ### Additional Beacon Fields