Skip to content

Commit

Permalink
Closes #923 - Added initiator beacon requirement to EUM Server (#924)
Browse files Browse the repository at this point in the history
  • Loading branch information
Jonas Kunz authored Sep 9, 2020
1 parent 5f0f70c commit 75c306c
Show file tree
Hide file tree
Showing 4 changed files with 162 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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<BeaconRequirement> 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));
}

/**
Expand All @@ -48,25 +47,36 @@ 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.
*/
@NotNull
private RequirementType requirement;

/**
* The list of allowed initiators for the {@link RequirementType#HAS_INITIATOR} requirement.
*/
private List<InitiatorType> 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) {
Expand All @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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"));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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);

Expand All @@ -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);

Expand All @@ -107,13 +108,58 @@ 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);

boolean result = requirement.validate(beacon);

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();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
...
Expand All @@ -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

Expand Down

0 comments on commit 75c306c

Please sign in to comment.