diff --git a/.generator/schemas/v1/openapi.yaml b/.generator/schemas/v1/openapi.yaml index 14469bd701c..ef660a7dfe1 100644 --- a/.generator/schemas/v1/openapi.yaml +++ b/.generator/schemas/v1/openapi.yaml @@ -12054,36 +12054,44 @@ components: type: object SLOCountDefinition: description: 'A count-based (metric) SLI specification, composed of three parts: - the good events formula, the total events formula, + the good events formula, - and the underlying queries.' + the bad or total events formula, and the underlying queries.' example: - good_events_formula: query1 - query2 + bad_events_formula: query2 + good_events_formula: query1 queries: - data_source: metrics name: query1 - query: sum:trace.servlet.request.hits{*} by {env}.as_count() + query: sum:trace.servlet.request.hits{http.status_code:2*} by {env}.as_count() - data_source: metrics name: query2 - query: sum:trace.servlet.request.errors{*} by {env}.as_count() - total_events_formula: query1 + query: sum:trace.servlet.request.hits{http.status_code:5*} by {env}.as_count() properties: + bad_events_formula: + $ref: '#/components/schemas/SLOFormula' + description: The bad events formula (recommended). Total events queries + can be defined using the `total_events_formula` field as an alternative. good_events_formula: $ref: '#/components/schemas/SLOFormula' queries: example: - data_source: metrics name: query1 - query: sum:trace.servlet.request.hits{*} by {env}.as_count() + query: sum:trace.servlet.request.hits{http.status_code:2*} by {env}.as_count() + - data_source: metrics + name: query2 + query: sum:trace.servlet.request.hits{http.status_code:5*} by {env}.as_count() items: $ref: '#/components/schemas/SLODataSourceQueryDefinition' minItems: 1 type: array total_events_formula: $ref: '#/components/schemas/SLOFormula' + description: The total events formula. Bad events queries can be defined + using the `bad_events_formula` field as an alternative. required: - good_events_formula - - total_events_formula - queries type: object SLOCountSpec: @@ -12091,15 +12099,15 @@ components: description: A metric SLI specification. example: count: - good_events_formula: query1 - query2 + bad_events_formula: query2 + good_events_formula: query1 queries: - data_source: metrics name: query1 - query: sum:trace.servlet.request.hits{*} by {env}.as_count() + query: sum:trace.servlet.request.hits{!http.status_code:5*} by {env}.as_count() - data_source: metrics name: query2 - query: sum:trace.servlet.request.errors{*} by {env}.as_count() - total_events_formula: query1 + query: sum:trace.servlet.request.hits{http.status_code:5*} by {env}.as_count() properties: count: $ref: '#/components/schemas/SLOCountDefinition' @@ -13235,7 +13243,7 @@ components: name: query1 query: sum:trace.servlet.request.hits{*} by {env}.as_count() - data_source: metrics - name: query1 + name: query2 query: sum:trace.servlet.request.errors{*} by {env}.as_count() threshold: 5 properties: diff --git a/examples/v1/service-level-objectives/CreateSLO_707861409.java b/examples/v1/service-level-objectives/CreateSLO_707861409.java new file mode 100644 index 00000000000..eabdfafe3e6 --- /dev/null +++ b/examples/v1/service-level-objectives/CreateSLO_707861409.java @@ -0,0 +1,76 @@ +// Create a new metric SLO object using bad events formula returns "OK" response + +import com.datadog.api.client.ApiClient; +import com.datadog.api.client.ApiException; +import com.datadog.api.client.v1.api.ServiceLevelObjectivesApi; +import com.datadog.api.client.v1.model.FormulaAndFunctionMetricDataSource; +import com.datadog.api.client.v1.model.FormulaAndFunctionMetricQueryDefinition; +import com.datadog.api.client.v1.model.SLOCountDefinition; +import com.datadog.api.client.v1.model.SLOCountSpec; +import com.datadog.api.client.v1.model.SLODataSourceQueryDefinition; +import com.datadog.api.client.v1.model.SLOFormula; +import com.datadog.api.client.v1.model.SLOListResponse; +import com.datadog.api.client.v1.model.SLOSliSpec; +import com.datadog.api.client.v1.model.SLOThreshold; +import com.datadog.api.client.v1.model.SLOTimeframe; +import com.datadog.api.client.v1.model.SLOType; +import com.datadog.api.client.v1.model.ServiceLevelObjectiveRequest; +import java.util.Arrays; +import java.util.Collections; + +public class Example { + public static void main(String[] args) { + ApiClient defaultClient = ApiClient.getDefaultApiClient(); + ServiceLevelObjectivesApi apiInstance = new ServiceLevelObjectivesApi(defaultClient); + + ServiceLevelObjectiveRequest body = + new ServiceLevelObjectiveRequest() + .type(SLOType.METRIC) + .description("Metric SLO using sli_specification") + .name("Example-Service-Level-Objective") + .sliSpecification( + new SLOSliSpec( + new SLOCountSpec() + .count( + new SLOCountDefinition() + .goodEventsFormula(new SLOFormula().formula("query1 - query2")) + .badEventsFormula(new SLOFormula().formula("query2")) + .queries( + Arrays.asList( + new SLODataSourceQueryDefinition( + new FormulaAndFunctionMetricQueryDefinition() + .dataSource( + FormulaAndFunctionMetricDataSource.METRICS) + .name("query1") + .query("sum:httpservice.hits{*}.as_count()")), + new SLODataSourceQueryDefinition( + new FormulaAndFunctionMetricQueryDefinition() + .dataSource( + FormulaAndFunctionMetricDataSource.METRICS) + .name("query2") + .query("sum:httpservice.errors{*}.as_count()"))))))) + .tags(Arrays.asList("env:prod", "type:count")) + .thresholds( + Collections.singletonList( + new SLOThreshold() + .target(99.0) + .targetDisplay("99.0") + .timeframe(SLOTimeframe.SEVEN_DAYS) + .warning(99.5) + .warningDisplay("99.5"))) + .timeframe(SLOTimeframe.SEVEN_DAYS) + .targetThreshold(99.0) + .warningThreshold(99.5); + + try { + SLOListResponse result = apiInstance.createSLO(body); + System.out.println(result); + } catch (ApiException e) { + System.err.println("Exception when calling ServiceLevelObjectivesApi#createSLO"); + System.err.println("Status code: " + e.getCode()); + System.err.println("Reason: " + e.getResponseBody()); + System.err.println("Response headers: " + e.getResponseHeaders()); + e.printStackTrace(); + } + } +} diff --git a/src/main/java/com/datadog/api/client/v1/model/SLOCountDefinition.java b/src/main/java/com/datadog/api/client/v1/model/SLOCountDefinition.java index d63c83045ef..54a3522e45e 100644 --- a/src/main/java/com/datadog/api/client/v1/model/SLOCountDefinition.java +++ b/src/main/java/com/datadog/api/client/v1/model/SLOCountDefinition.java @@ -21,9 +21,10 @@ /** * A count-based (metric) SLI specification, composed of three parts: the good events formula, the - * total events formula, and the underlying queries. + * bad or total events formula, and the underlying queries. */ @JsonPropertyOrder({ + SLOCountDefinition.JSON_PROPERTY_BAD_EVENTS_FORMULA, SLOCountDefinition.JSON_PROPERTY_GOOD_EVENTS_FORMULA, SLOCountDefinition.JSON_PROPERTY_QUERIES, SLOCountDefinition.JSON_PROPERTY_TOTAL_EVENTS_FORMULA @@ -32,6 +33,9 @@ value = "https://github.com/DataDog/datadog-api-client-java/blob/master/.generator") public class SLOCountDefinition { @JsonIgnore public boolean unparsed = false; + public static final String JSON_PROPERTY_BAD_EVENTS_FORMULA = "bad_events_formula"; + private SLOFormula badEventsFormula; + public static final String JSON_PROPERTY_GOOD_EVENTS_FORMULA = "good_events_formula"; private SLOFormula goodEventsFormula; @@ -48,14 +52,32 @@ public SLOCountDefinition( @JsonProperty(required = true, value = JSON_PROPERTY_GOOD_EVENTS_FORMULA) SLOFormula goodEventsFormula, @JsonProperty(required = true, value = JSON_PROPERTY_QUERIES) - List queries, - @JsonProperty(required = true, value = JSON_PROPERTY_TOTAL_EVENTS_FORMULA) - SLOFormula totalEventsFormula) { + List queries) { this.goodEventsFormula = goodEventsFormula; this.unparsed |= goodEventsFormula.unparsed; this.queries = queries; - this.totalEventsFormula = totalEventsFormula; - this.unparsed |= totalEventsFormula.unparsed; + } + + public SLOCountDefinition badEventsFormula(SLOFormula badEventsFormula) { + this.badEventsFormula = badEventsFormula; + this.unparsed |= badEventsFormula.unparsed; + return this; + } + + /** + * A formula that specifies how to combine the results of multiple queries. + * + * @return badEventsFormula + */ + @jakarta.annotation.Nullable + @JsonProperty(JSON_PROPERTY_BAD_EVENTS_FORMULA) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) + public SLOFormula getBadEventsFormula() { + return badEventsFormula; + } + + public void setBadEventsFormula(SLOFormula badEventsFormula) { + this.badEventsFormula = badEventsFormula; } public SLOCountDefinition goodEventsFormula(SLOFormula goodEventsFormula) { @@ -119,8 +141,9 @@ public SLOCountDefinition totalEventsFormula(SLOFormula totalEventsFormula) { * * @return totalEventsFormula */ + @jakarta.annotation.Nullable @JsonProperty(JSON_PROPERTY_TOTAL_EVENTS_FORMULA) - @JsonInclude(value = JsonInclude.Include.ALWAYS) + @JsonInclude(value = JsonInclude.Include.USE_DEFAULTS) public SLOFormula getTotalEventsFormula() { return totalEventsFormula; } @@ -185,7 +208,8 @@ public boolean equals(Object o) { return false; } SLOCountDefinition sloCountDefinition = (SLOCountDefinition) o; - return Objects.equals(this.goodEventsFormula, sloCountDefinition.goodEventsFormula) + return Objects.equals(this.badEventsFormula, sloCountDefinition.badEventsFormula) + && Objects.equals(this.goodEventsFormula, sloCountDefinition.goodEventsFormula) && Objects.equals(this.queries, sloCountDefinition.queries) && Objects.equals(this.totalEventsFormula, sloCountDefinition.totalEventsFormula) && Objects.equals(this.additionalProperties, sloCountDefinition.additionalProperties); @@ -193,13 +217,15 @@ public boolean equals(Object o) { @Override public int hashCode() { - return Objects.hash(goodEventsFormula, queries, totalEventsFormula, additionalProperties); + return Objects.hash( + badEventsFormula, goodEventsFormula, queries, totalEventsFormula, additionalProperties); } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("class SLOCountDefinition {\n"); + sb.append(" badEventsFormula: ").append(toIndentedString(badEventsFormula)).append("\n"); sb.append(" goodEventsFormula: ").append(toIndentedString(goodEventsFormula)).append("\n"); sb.append(" queries: ").append(toIndentedString(queries)).append("\n"); sb.append(" totalEventsFormula: ").append(toIndentedString(totalEventsFormula)).append("\n"); diff --git a/src/main/java/com/datadog/api/client/v1/model/SLOCountSpec.java b/src/main/java/com/datadog/api/client/v1/model/SLOCountSpec.java index d11603840c5..26cad1732f1 100644 --- a/src/main/java/com/datadog/api/client/v1/model/SLOCountSpec.java +++ b/src/main/java/com/datadog/api/client/v1/model/SLOCountSpec.java @@ -39,7 +39,7 @@ public SLOCountSpec count(SLOCountDefinition count) { /** * A count-based (metric) SLI specification, composed of three parts: the good events formula, the - * total events formula, and the underlying queries. + * bad or total events formula, and the underlying queries. * * @return count */ diff --git a/src/test/resources/cassettes/features/v1/Create_a_new_metric_SLO_object_using_bad_events_formula_returns_OK_response.freeze b/src/test/resources/cassettes/features/v1/Create_a_new_metric_SLO_object_using_bad_events_formula_returns_OK_response.freeze new file mode 100644 index 00000000000..b1ab9b09452 --- /dev/null +++ b/src/test/resources/cassettes/features/v1/Create_a_new_metric_SLO_object_using_bad_events_formula_returns_OK_response.freeze @@ -0,0 +1 @@ +2026-02-25T17:45:38.518Z \ No newline at end of file diff --git a/src/test/resources/cassettes/features/v1/Create_a_new_metric_SLO_object_using_bad_events_formula_returns_OK_response.json b/src/test/resources/cassettes/features/v1/Create_a_new_metric_SLO_object_using_bad_events_formula_returns_OK_response.json new file mode 100644 index 00000000000..92a340d8d59 --- /dev/null +++ b/src/test/resources/cassettes/features/v1/Create_a_new_metric_SLO_object_using_bad_events_formula_returns_OK_response.json @@ -0,0 +1,58 @@ +[ + { + "httpRequest": { + "body": { + "type": "JSON", + "json": "{\"description\": \"Metric SLO using sli_specification\", \"name\": \"Test-Create_a_new_metric_SLO_object_using_bad_events_formula_returns_OK_response-1772041538\", \"sli_specification\": {\"count\": {\"bad_events_formula\": {\"formula\": \"query2\"}, \"good_events_formula\": {\"formula\": \"query1 - query2\"}, \"queries\": [{\"data_source\": \"metrics\", \"name\": \"query1\", \"query\": \"sum:httpservice.hits{*}.as_count()\"}, {\"data_source\": \"metrics\", \"name\": \"query2\", \"query\": \"sum:httpservice.errors{*}.as_count()\"}]}}, \"tags\": [\"env:prod\", \"type:count\"], \"target_threshold\": 99, \"thresholds\": [{\"target\": 99.0, \"target_display\": \"99.0\", \"timeframe\": \"7d\", \"warning\": 99.5, \"warning_display\": \"99.5\"}], \"timeframe\": \"7d\", \"type\": \"metric\", \"warning_threshold\": 99.5}" + }, + "headers": {}, + "method": "POST", + "path": "/api/v1/slo", + "keepAlive": false, + "secure": true + }, + "httpResponse": { + "body": "{\"data\":[{\"id\":\"7309ff3752fd519f80f65a2ed3247dbb\",\"name\":\"Test-Create_a_new_metric_SLO_object_using_bad_events_formula_returns_OK_response-1772041538\",\"tags\":[\"env:prod\",\"type:count\"],\"monitor_tags\":[],\"thresholds\":[{\"timeframe\":\"7d\",\"target\":99.0,\"target_display\":\"99.\",\"warning\":99.5,\"warning_display\":\"99.5\"}],\"type\":\"metric\",\"type_id\":1,\"description\":\"Metric SLO using sli_specification\",\"timeframe\":\"7d\",\"warning_threshold\":99.5,\"target_threshold\":99,\"query\":{\"numerator\":\"sum:httpservice.hits{*}.as_count() - sum:httpservice.errors{*}.as_count()\",\"denominator\":\"(sum:httpservice.hits{*}.as_count() - sum:httpservice.errors{*}.as_count()) + (sum:httpservice.errors{*}.as_count())\"},\"creator\":{\"name\":\"CI Account\",\"handle\":\"9919ec9b-ebc7-49ee-8dc8-03626e717cca\",\"email\":\"team-intg-tools-libs-spam@datadoghq.com\"},\"created_at\":1772041538,\"modified_at\":1772041538,\"sli_specification\":{\"count\":{\"bad_events_formula\":{\"formula\":\"query2\"},\"good_events_formula\":{\"formula\":\"query1 - query2\"},\"queries\":[{\"data_source\":\"metrics\",\"name\":\"query1\",\"query\":\"sum:httpservice.hits{*}.as_count()\"},{\"data_source\":\"metrics\",\"name\":\"query2\",\"query\":\"sum:httpservice.errors{*}.as_count()\"}]}}}],\"error\":null}\n", + "headers": { + "Content-Type": [ + "application/json" + ] + }, + "statusCode": 200, + "reasonPhrase": "OK" + }, + "times": { + "remainingTimes": 1 + }, + "timeToLive": { + "unlimited": true + }, + "id": "d5693d5e-ee1e-afa7-88d0-69277375e5d9" + }, + { + "httpRequest": { + "headers": {}, + "method": "DELETE", + "path": "/api/v1/slo/7309ff3752fd519f80f65a2ed3247dbb", + "keepAlive": false, + "secure": true + }, + "httpResponse": { + "body": "{\"data\":[\"7309ff3752fd519f80f65a2ed3247dbb\"],\"error\":null}\n", + "headers": { + "Content-Type": [ + "application/json" + ] + }, + "statusCode": 200, + "reasonPhrase": "OK" + }, + "times": { + "remainingTimes": 1 + }, + "timeToLive": { + "unlimited": true + }, + "id": "c0933b3f-30b7-b24a-0ddb-dcdf3c1ea62d" + } +] \ No newline at end of file diff --git a/src/test/resources/com/datadog/api/client/v1/api/service_level_objectives.feature b/src/test/resources/com/datadog/api/client/v1/api/service_level_objectives.feature index 22c9d6cd3f9..6e756c5a54e 100644 --- a/src/test/resources/com/datadog/api/client/v1/api/service_level_objectives.feature +++ b/src/test/resources/com/datadog/api/client/v1/api/service_level_objectives.feature @@ -48,6 +48,19 @@ Feature: Service Level Objectives When the request is sent Then the response status is 200 OK + @team:DataDog/slo-app + Scenario: Create a new metric SLO object using bad events formula returns "OK" response + Given new "CreateSLO" request + And body with value {"type":"metric","description":"Metric SLO using sli_specification","name":"{{ unique }}","sli_specification":{"count":{"good_events_formula":{"formula":"query1 - query2"},"bad_events_formula":{"formula":"query2"},"queries":[{"data_source":"metrics","name":"query1","query":"sum:httpservice.hits{*}.as_count()"},{"data_source":"metrics","name":"query2","query":"sum:httpservice.errors{*}.as_count()"}]}},"tags":["env:prod","type:count"],"thresholds":[{"target":99.0,"target_display":"99.0","timeframe":"7d","warning":99.5,"warning_display":"99.5"}],"timeframe":"7d","target_threshold":99.0,"warning_threshold":99.5} + When the request is sent + Then the response status is 200 OK + And the response "data[0]" has field "sli_specification" + And the response "data[0].sli_specification" has field "count" + And the response "data[0].sli_specification.count" has field "good_events_formula" + And the response "data[0].sli_specification.count" has field "bad_events_formula" + And the response "data[0].sli_specification.count" has field "queries" + And the response "data[0].sli_specification.count.queries" has length 2 + @team:DataDog/slo-app Scenario: Create a new metric SLO object using sli_specification returns "OK" response Given new "CreateSLO" request @@ -247,7 +260,7 @@ Feature: Service Level Objectives Scenario: Update an SLO returns "Not Found" response Given new "UpdateSLO" request And request contains "slo_id" parameter from "REPLACE.ME" - And body with value {"description": null, "groups": ["env:prod", "role:mysql"], "monitor_ids": [], "monitor_tags": [], "name": "Custom Metric SLO", "query": {"denominator": "sum:my.custom.metric{*}.as_count()", "numerator": "sum:my.custom.metric{type:good}.as_count()"}, "sli_specification": {"time_slice": {"comparator": "<", "query": {"formulas": [{"formula": "query2/query1"}], "queries": [{"data_source": "metrics", "name": "query1", "query": "sum:trace.servlet.request.hits{*} by {env}.as_count()"}, {"data_source": "metrics", "name": "query1", "query": "sum:trace.servlet.request.errors{*} by {env}.as_count()"}]}, "threshold": 5}}, "tags": ["env:prod", "app:core"], "target_threshold": 99.9, "thresholds": [{"target": 95, "timeframe": "7d"}, {"target": 95, "timeframe": "30d", "warning": 97}], "timeframe": "30d", "type": "metric", "warning_threshold": 99.95} + And body with value {"description": null, "groups": ["env:prod", "role:mysql"], "monitor_ids": [], "monitor_tags": [], "name": "Custom Metric SLO", "query": {"denominator": "sum:my.custom.metric{*}.as_count()", "numerator": "sum:my.custom.metric{type:good}.as_count()"}, "sli_specification": {"time_slice": {"comparator": "<", "query": {"formulas": [{"formula": "query2/query1"}], "queries": [{"data_source": "metrics", "name": "query1", "query": "sum:trace.servlet.request.hits{*} by {env}.as_count()"}, {"data_source": "metrics", "name": "query2", "query": "sum:trace.servlet.request.errors{*} by {env}.as_count()"}]}, "threshold": 5}}, "tags": ["env:prod", "app:core"], "target_threshold": 99.9, "thresholds": [{"target": 95, "timeframe": "7d"}, {"target": 95, "timeframe": "30d", "warning": 97}], "timeframe": "30d", "type": "metric", "warning_threshold": 99.95} When the request is sent Then the response status is 404 Not Found