Skip to content

Commit

Permalink
Add Change Detection Config Discriminators: Fixes Hyperfoil#1380
Browse files Browse the repository at this point in the history
  • Loading branch information
johnaohara authored and stalep committed Feb 26, 2024
1 parent 794b714 commit 15d78ed
Show file tree
Hide file tree
Showing 14 changed files with 423 additions and 173 deletions.
84 changes: 68 additions & 16 deletions docs/site/content/en/openapi/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2372,8 +2372,19 @@ components:
config:
type: object
oneOf:
- $ref: '#/components/schemas/RelativeDifferenceDetection'
- $ref: '#/components/schemas/FixedThresholdDetection'
- $ref: '#/components/schemas/RelativeDifferenceDetectionConfig'
- $ref: '#/components/schemas/FixedThresholdDetectionConfig'
discriminator:
propertyName: model
mapping:
relativeDifference: '#/components/schemas/RelativeDifferenceDetectionConfig'
fixedThreshold: '#/components/schemas/FixedThresholdDetectionConfig'
ChangeDetectionModelType:
description: Type of Change Detection Model
enum:
- FIXED_THRESHOLD
- RELATIVE_DIFFERENCE
type: string
ComparisonResult:
description: Result of performing a Comparison
type: object
Expand Down Expand Up @@ -2961,13 +2972,45 @@ components:
type: array
items:
$ref: '#/components/schemas/FingerprintValue'
FixedThresholdDetection:
FixThresholdConfig:
required:
- value
- enabled
- inclusive
type: object
properties:
value:
description: Threshold Value
type: integer
example: 95
enabled:
description: Threshold enabled/disabled
type: boolean
example: true
inclusive:
description: Is threshold inclusive of defined value?
type: boolean
example: false
FixedThresholdDetectionConfig:
required:
- builtIn
- min
- max
type: object
properties:
builtIn:
description: Built In
type: boolean
min:
$ref: '#/components/schemas/Threshold'
description: Lower bound for acceptable datapoint values
type: object
allOf:
- $ref: '#/components/schemas/FixThresholdConfig'
max:
$ref: '#/components/schemas/Threshold'
description: Upper bound for acceptable datapoint values
type: object
allOf:
- $ref: '#/components/schemas/FixThresholdConfig'
GithubIssueCommentAction:
type: object
properties:
Expand Down Expand Up @@ -3348,20 +3391,39 @@ components:
description: Total number of generated datasets
type: integer
example: 186
RelativeDifferenceDetection:
RelativeDifferenceDetectionConfig:
required:
- builtIn
- filter
- window
- threshold
- minPrevious
type: object
properties:
builtIn:
description: Built In
type: boolean
filter:
description: Relative Difference Detection filter
type: string
example: mean
window:
format: int32
description: Number of most recent datapoints used for aggregating the value
for comparison.
type: integer
example: 5
threshold:
format: double
description: Maximum difference between the aggregated value of last <window>
datapoints and the mean of preceding values.
type: number
example: 0.2
minPrevious:
format: int32
description: Minimal number of preceding datapoints
type: integer
example: 5
ReportComponent:
description: Report Component
required:
Expand Down Expand Up @@ -4322,16 +4384,6 @@ components:
description: Token description
type: string
example: my reporting service token
Threshold:
type: object
properties:
value:
format: int32
type: integer
enabled:
type: boolean
inclusive:
type: boolean
TransformationLog:
description: Transformation Log
required:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,44 +1,31 @@
package io.hyperfoil.tools.horreum.api.alerting;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.hyperfoil.tools.horreum.api.data.changeDetection.FixedThresholdDetectionConfig;
import io.hyperfoil.tools.horreum.api.data.changeDetection.RelativeDifferenceDetectionConfig;
import jakarta.validation.constraints.NotNull;
import org.eclipse.microprofile.openapi.annotations.enums.SchemaType;
import org.eclipse.microprofile.openapi.annotations.media.DiscriminatorMapping;
import org.eclipse.microprofile.openapi.annotations.media.Schema;

public class ChangeDetection {

/* This is another hack, it is based on io.hyperfoil.tools.horreum.changedetection.RelativeDifferenceChangeDetectionModel.config
* We should coordinate type information with that method but openapi needs statically typed classes and that method
* dynamically builds the ConditionConfig
*/
public static class RelativeDifferenceDetection {
public String filter;//mean,min,max
public Integer window;//1
public Double threshold;//0.2
public Integer minPrevious;//1
}
public static class Threshold {
Integer value;
Boolean enabled;
Boolean inclusive;
}
public static class FixedThresholdDetection {
public Threshold min;
public Threshold max;
}

@JsonProperty( required = true )
public Integer id;
@JsonProperty( required = true )
public String model;
@NotNull
@JsonProperty( required = true )
@Schema(type = SchemaType.OBJECT,
@Schema(type = SchemaType.OBJECT, discriminatorProperty = "model",
discriminatorMapping = {
@DiscriminatorMapping(schema = RelativeDifferenceDetectionConfig.class, value = "relativeDifference"),
@DiscriminatorMapping(schema = FixedThresholdDetectionConfig.class, value = "fixedThreshold")
},
oneOf = {
ChangeDetection.RelativeDifferenceDetection.class,
ChangeDetection.FixedThresholdDetection.class
RelativeDifferenceDetectionConfig.class,
FixedThresholdDetectionConfig.class
}
)
public ObjectNode config;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package io.hyperfoil.tools.horreum.api.data.changeDetection;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.core.type.TypeReference;
import io.hyperfoil.tools.horreum.api.data.datastore.BaseChangeDetectionConfig;
import org.eclipse.microprofile.openapi.annotations.enums.SchemaType;
import org.eclipse.microprofile.openapi.annotations.media.Schema;

import java.util.Arrays;
import java.util.Optional;

@Schema(type = SchemaType.STRING, required = true,
description = "Type of Change Detection Model")
public enum ChangeDetectionModelType {
FIXED_THRESHOLD("fixedThreshold", new TypeReference<FixedThresholdDetectionConfig>() {}),
RELATIVE_DIFFERENCE ("relativeDifference", new TypeReference<RelativeDifferenceDetectionConfig>() {});
private static final ChangeDetectionModelType[] VALUES = values();

private final String name;
private final TypeReference<? extends BaseChangeDetectionConfig> typeReference;

private <T extends BaseChangeDetectionConfig> ChangeDetectionModelType(String name, TypeReference<T> typeReference) {
this.typeReference = typeReference;
this.name = name;
}

public <T extends BaseChangeDetectionConfig> TypeReference<T> getTypeReference(){
return (TypeReference<T>) typeReference;
}

@JsonCreator
public static ChangeDetectionModelType fromString(String str) {
return Arrays.stream(VALUES).filter(v -> v.name.equals(str)).findAny().orElseThrow(() -> new IllegalArgumentException("Unknown model: " + str));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package io.hyperfoil.tools.horreum.api.data.changeDetection;

import org.eclipse.microprofile.openapi.annotations.enums.SchemaType;
import org.eclipse.microprofile.openapi.annotations.media.Schema;

/*
* Concrete configuration type for io.hyperfoil.tools.horreum.changedetection.FixedThresholdModel
*/
public class FixThresholdConfig {
@Schema(type = SchemaType.STRING, required = true, example = "fixedThreshold",
description = "model descriminator")
public static final String model = "fixedThreshold";
@Schema(type = SchemaType.INTEGER, required = true, example = "95",
description = "Threshold Value")
public Double value;
@Schema(type = SchemaType.BOOLEAN, required = true, example = "true",
description = "Threshold enabled/disabled")
public Boolean enabled;
@Schema(type = SchemaType.BOOLEAN, required = true, example = "false",
description = "Is threshold inclusive of defined value?")
public Boolean inclusive;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package io.hyperfoil.tools.horreum.api.data.changeDetection;

import io.hyperfoil.tools.horreum.api.data.datastore.BaseChangeDetectionConfig;
import org.eclipse.microprofile.openapi.annotations.enums.SchemaType;
import org.eclipse.microprofile.openapi.annotations.media.Schema;

public class FixedThresholdDetectionConfig extends BaseChangeDetectionConfig {
@Schema(type = SchemaType.OBJECT, required = true,
description = "Lower bound for acceptable datapoint values")
public FixThresholdConfig min;
@Schema(type = SchemaType.OBJECT, required = true,
description = "Upper bound for acceptable datapoint values")
public FixThresholdConfig max;

@Override
public String validateConfig() {
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package io.hyperfoil.tools.horreum.api.data.changeDetection;

import io.hyperfoil.tools.horreum.api.data.datastore.BaseChangeDetectionConfig;
import org.eclipse.microprofile.openapi.annotations.enums.SchemaType;
import org.eclipse.microprofile.openapi.annotations.media.Schema;

/*
* Concrete configuration type for io.hyperfoil.tools.horreum.changedetection.RelativeDifferenceChangeDetectionModel
*/
public class RelativeDifferenceDetectionConfig extends BaseChangeDetectionConfig {
@Schema(type = SchemaType.STRING, required = true, example = "relativeDifference",
description = "model descriminator")
public static final String model = "relativeDifference";
@Schema(type = SchemaType.STRING, required = true, example = "mean",
description = "Relative Difference Detection filter")
public String filter;
@Schema(type = SchemaType.INTEGER, required = true, example = "5",
description = "Number of most recent datapoints used for aggregating the value for comparison.")
public Integer window;
@Schema(type = SchemaType.NUMBER, required = true, example = "0.2",
description = "Maximum difference between the aggregated value of last <window> datapoints and the mean of preceding values.")
public Double threshold;
@Schema(type = SchemaType.INTEGER, required = true, example = "5",
description = "Minimal number of preceding datapoints")
public Integer minPrevious;

@Override
public String validateConfig() {
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package io.hyperfoil.tools.horreum.api.data.datastore;

import org.eclipse.microprofile.openapi.annotations.enums.SchemaType;
import org.eclipse.microprofile.openapi.annotations.media.Schema;

public abstract class BaseChangeDetectionConfig {

@Schema(type = SchemaType.BOOLEAN, required = true,
description = "Built In")
public Boolean builtIn = true;

public BaseChangeDetectionConfig() {
}

public BaseChangeDetectionConfig(Boolean builtIn) {
this.builtIn = builtIn;
}

public abstract String validateConfig();
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
package io.hyperfoil.tools.horreum.changedetection;

import com.fasterxml.jackson.databind.JsonNode;
import io.hyperfoil.tools.horreum.api.data.ConditionConfig;
import io.hyperfoil.tools.horreum.api.data.changeDetection.ChangeDetectionModelType;
import io.hyperfoil.tools.horreum.entity.alerting.ChangeDAO;
import io.hyperfoil.tools.horreum.entity.alerting.DataPointDAO;

import java.util.List;
import java.util.function.Consumer;

import com.fasterxml.jackson.databind.JsonNode;

public interface ChangeDetectionModel {
ConditionConfig config();

ChangeDetectionModelType type();
void analyze(List<DataPointDAO> dataPoints, JsonNode configuration, Consumer<ChangeDAO> changeConsumer);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package io.hyperfoil.tools.horreum.changedetection;

import io.hyperfoil.tools.horreum.api.data.changeDetection.ChangeDetectionModelType;
import io.quarkus.arc.All;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;

import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

@ApplicationScoped
public class ChangeDetectionModelResolver {
@Inject
@All
List<ChangeDetectionModel> changeDetectionModels;

public ChangeDetectionModel getModel(ChangeDetectionModelType type){
return changeDetectionModels.stream()
.filter( model -> model.type().equals(type))
.findFirst()
.orElseThrow(() -> new IllegalStateException("Unknown change detection model type: " + type));
}

public Map<String, ChangeDetectionModel> getModels(){
return changeDetectionModels.stream()
.collect(Collectors.toMap(model -> ((ChangeDetectionModel)model).type().name(), Function.identity()));
}


}
Loading

0 comments on commit 15d78ed

Please sign in to comment.