From 3311f6de324cc9c061749be2120d9cd6da50dd51 Mon Sep 17 00:00:00 2001 From: pmarupaka Date: Fri, 31 Jul 2026 16:31:54 -0400 Subject: [PATCH] Add ORCA --- orca/build.gradle | 13 ++ .../java/api/orca/ImmutablesStyle.java | 32 +++ .../conjure/java/api/orca/OrcaLoadReport.java | 85 +++++++ .../java/api/orca/OrcaLoadReports.java | 210 ++++++++++++++++++ .../java/api/orca/OrcaLoadReportTest.java | 74 ++++++ .../java/api/orca/OrcaLoadReportsTest.java | 189 ++++++++++++++++ settings.gradle | 1 + 7 files changed, 604 insertions(+) create mode 100644 orca/build.gradle create mode 100644 orca/src/main/java/com/palantir/conjure/java/api/orca/ImmutablesStyle.java create mode 100644 orca/src/main/java/com/palantir/conjure/java/api/orca/OrcaLoadReport.java create mode 100644 orca/src/main/java/com/palantir/conjure/java/api/orca/OrcaLoadReports.java create mode 100644 orca/src/test/java/com/palantir/conjure/java/api/orca/OrcaLoadReportTest.java create mode 100644 orca/src/test/java/com/palantir/conjure/java/api/orca/OrcaLoadReportsTest.java diff --git a/orca/build.gradle b/orca/build.gradle new file mode 100644 index 000000000..7513dbe25 --- /dev/null +++ b/orca/build.gradle @@ -0,0 +1,13 @@ +apply plugin: 'com.palantir.external-publish-jar' +apply plugin: 'com.palantir.revapi' + +dependencies { + annotationProcessor "org.immutables:value" + + compileOnlyApi 'org.immutables:value::annotations' + + implementation 'com.palantir.safe-logging:preconditions' + + testImplementation "org.assertj:assertj-core" + testImplementation 'org.junit.jupiter:junit-jupiter' +} diff --git a/orca/src/main/java/com/palantir/conjure/java/api/orca/ImmutablesStyle.java b/orca/src/main/java/com/palantir/conjure/java/api/orca/ImmutablesStyle.java new file mode 100644 index 000000000..9b8a4a2f6 --- /dev/null +++ b/orca/src/main/java/com/palantir/conjure/java/api/orca/ImmutablesStyle.java @@ -0,0 +1,32 @@ +/* + * (c) Copyright 2026 Palantir Technologies Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.palantir.conjure.java.api.orca; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import org.immutables.value.Value; + +// Same as service-config/src/main/java/com/palantir/conjure/java/api/config/service/ImmutablesStyle.java +// Keeps ImmutableOrcaLoadReport package-private +// Exposes only OrcaLoadReport.Builder +// Uses JDK collection types (as opposed to Guava) +@Target({ElementType.PACKAGE, ElementType.TYPE}) +@Retention(RetentionPolicy.SOURCE) +@Value.Style(visibility = Value.Style.ImplementationVisibility.PACKAGE, overshadowImplementation = true, jdkOnly = true) +@interface ImmutablesStyle {} diff --git a/orca/src/main/java/com/palantir/conjure/java/api/orca/OrcaLoadReport.java b/orca/src/main/java/com/palantir/conjure/java/api/orca/OrcaLoadReport.java new file mode 100644 index 000000000..ef65c2ff3 --- /dev/null +++ b/orca/src/main/java/com/palantir/conjure/java/api/orca/OrcaLoadReport.java @@ -0,0 +1,85 @@ +/* + * (c) Copyright 2026 Palantir Technologies Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.palantir.conjure.java.api.orca; + +import com.palantir.logsafe.SafeArg; +import com.palantir.logsafe.exceptions.SafeIllegalArgumentException; +import java.util.Map; +import java.util.OptionalDouble; +import org.immutables.value.Value; + +/** A transport-independent representation of an Open Request Cost Aggregation (ORCA) load report. */ +@Value.Immutable +@ImmutablesStyle +public interface OrcaLoadReport { + + /** CPU utilization as a fraction of available CPU resources. Values may exceed one. */ + OptionalDouble cpuUtilization(); + + /** Memory utilization as a fraction of available memory resources. */ + OptionalDouble memUtilization(); + + /** Total requests per second served by the endpoint. */ + OptionalDouble rpsFractional(); + + /** Total errors per second served by the endpoint. */ + OptionalDouble eps(); + + /** Application-specific opaque metrics. */ + Map namedMetrics(); + + /** Application-specific utilization as a fraction of available resources. Values may exceed one. */ + OptionalDouble applicationUtilization(); + + @Value.Check + default void check() { + cpuUtilization().ifPresent(value -> checkNonNegative("cpuUtilization", value)); + memUtilization().ifPresent(value -> checkFraction("memUtilization", value)); + rpsFractional().ifPresent(value -> checkNonNegative("rpsFractional", value)); + eps().ifPresent(value -> checkNonNegative("eps", value)); + namedMetrics().forEach(OrcaLoadReport::checkFinite); + applicationUtilization().ifPresent(value -> checkNonNegative("applicationUtilization", value)); + } + + static Builder builder() { + return new Builder(); + } + + final class Builder extends ImmutableOrcaLoadReport.Builder {} + + private static void checkNonNegative(String field, double value) { + checkFinite(field, value); + if (value < 0.0) { + throw new SafeIllegalArgumentException( + "ORCA metric must be greater than or equal to zero", SafeArg.of("metric", field)); + } + } + + private static void checkFraction(String field, double value) { + checkNonNegative(field, value); + if (value > 1.0) { + throw new SafeIllegalArgumentException( + "ORCA metric must be less than or equal to one", SafeArg.of("metric", field)); + } + } + + private static void checkFinite(String field, double value) { + if (!Double.isFinite(value)) { + throw new SafeIllegalArgumentException("ORCA metric must be finite", SafeArg.of("metric", field)); + } + } +} diff --git a/orca/src/main/java/com/palantir/conjure/java/api/orca/OrcaLoadReports.java b/orca/src/main/java/com/palantir/conjure/java/api/orca/OrcaLoadReports.java new file mode 100644 index 000000000..6469cce61 --- /dev/null +++ b/orca/src/main/java/com/palantir/conjure/java/api/orca/OrcaLoadReports.java @@ -0,0 +1,210 @@ +/* + * (c) Copyright 2026 Palantir Technologies Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.palantir.conjure.java.api.orca; + +import com.palantir.logsafe.exceptions.SafeIllegalArgumentException; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; +import java.util.StringJoiner; + +/** + * Utilities for encoding and decoding ORCA load reports using the {@code endpoint-load-metrics} HTTP response header. + * Values use a comma-separated format beginning with the literal {@code TEXT } prefix, for example + * {@code TEXT cpu_utilization=0.3,mem_utilization=0.8,named_metrics.queue_depth=12.0}. Custom + * {@code named_metrics.} keys are percent-encoded so reserved characters ({@code , = :}) round-trip safely. + */ +public final class OrcaLoadReports { + + private static final String LOAD_METRICS_HEADER = "endpoint-load-metrics"; + private static final String TEXT_PREFIX = "TEXT "; + private static final String NAMED_METRICS_PREFIX = "named_metrics."; + + public static void encodeToResponse( + OrcaLoadReport report, T response, OrcaResponseEncodingAdapter adapter) { + if (isEmpty(report)) { + return; + } + adapter.setHeader(response, LOAD_METRICS_HEADER, encode(report)); + } + + public static Optional parseFromResponse( + T response, OrcaResponseDecodingAdapter adapter) { + Optional header = adapter.getFirstHeader(response, LOAD_METRICS_HEADER); + if (header.isEmpty()) { + return Optional.empty(); + } + return decode(header.get()); + } + + public interface OrcaResponseEncodingAdapter { + void setHeader(RESPONSE response, String headerName, String headerValue); + } + + public interface OrcaResponseDecodingAdapter { + Optional getFirstHeader(RESPONSE response, String headerName); + } + + private static String encode(OrcaLoadReport report) { + StringJoiner result = new StringJoiner(",", TEXT_PREFIX, ""); + if (report.cpuUtilization().isPresent()) { + result.add("cpu_utilization=" + report.cpuUtilization().getAsDouble()); + } + if (report.memUtilization().isPresent()) { + result.add("mem_utilization=" + report.memUtilization().getAsDouble()); + } + if (report.rpsFractional().isPresent()) { + result.add("rps_fractional=" + report.rpsFractional().getAsDouble()); + } + if (report.eps().isPresent()) { + result.add("eps=" + report.eps().getAsDouble()); + } + if (report.applicationUtilization().isPresent()) { + result.add( + "application_utilization=" + report.applicationUtilization().getAsDouble()); + } + report.namedMetrics() + .forEach((name, value) -> result.add(NAMED_METRICS_PREFIX + encodeMetricName(name) + '=' + value)); + return result.toString(); + } + + private static Optional decode(String header) { + if (!header.startsWith(TEXT_PREFIX)) { + return Optional.empty(); + } + try { + OrcaLoadReport.Builder builder = OrcaLoadReport.builder(); + Set fields = new HashSet<>(); + String value = header.substring(TEXT_PREFIX.length()); + if (value.isEmpty()) { + return Optional.empty(); + } + for (String entry : value.split(",", -1)) { + parseEntry(builder, fields, entry); + } + OrcaLoadReport report = builder.build(); + return isEmpty(report) ? Optional.empty() : Optional.of(report); + } catch (IllegalArgumentException _exception) { + return Optional.empty(); + } + } + + private static void parseEntry(OrcaLoadReport.Builder builder, Set fields, String entry) { + int separator = separatorIndex(entry); + if (separator <= 0 || separator == entry.length() - 1) { + throw new SafeIllegalArgumentException("Invalid ORCA metric"); + } + String name = entry.substring(0, separator); + boolean namedMetric = name.startsWith(NAMED_METRICS_PREFIX); + if (!namedMetric && !isRecognized(name)) { + return; + } + checkDuplicate(fields, name); + double value; + try { + value = Double.parseDouble(entry.substring(separator + 1)); + } catch (NumberFormatException _exception) { + return; + } + if (namedMetric) { + if (Double.isFinite(value)) { + builder.putNamedMetrics(decodeMetricName(name), value); + } + return; + } + switch (name) { + case "cpu_utilization" -> { + if (isNonNegative(value)) { + builder.cpuUtilization(value); + } + } + case "mem_utilization" -> { + if (isFraction(value)) { + builder.memUtilization(value); + } + } + case "rps_fractional" -> { + if (isNonNegative(value)) { + builder.rpsFractional(value); + } + } + case "eps" -> { + if (isNonNegative(value)) { + builder.eps(value); + } + } + case "application_utilization" -> { + if (isNonNegative(value)) { + builder.applicationUtilization(value); + } + } + default -> throw new SafeIllegalArgumentException("Invalid ORCA metric"); + } + } + + private static int separatorIndex(String entry) { + int equals = entry.indexOf('='); + int colon = entry.indexOf(':'); + if (equals < 0) { + return colon; + } + return colon < 0 ? equals : Math.min(equals, colon); + } + + private static boolean isRecognized(String name) { + return switch (name) { + case "cpu_utilization", "mem_utilization", "rps_fractional", "eps", "application_utilization" -> true; + default -> false; + }; + } + + private static boolean isNonNegative(double value) { + return Double.isFinite(value) && value >= 0.0; + } + + private static boolean isFraction(double value) { + return isNonNegative(value) && value <= 1.0; + } + + private static String encodeMetricName(String name) { + return URLEncoder.encode(name, StandardCharsets.UTF_8); + } + + private static String decodeMetricName(String name) { + return URLDecoder.decode(name.substring(NAMED_METRICS_PREFIX.length()), StandardCharsets.UTF_8); + } + + private static void checkDuplicate(Set fields, String field) { + if (!fields.add(field)) { + throw new SafeIllegalArgumentException("Duplicate ORCA metric"); + } + } + + private static boolean isEmpty(OrcaLoadReport report) { + return report.cpuUtilization().isEmpty() + && report.memUtilization().isEmpty() + && report.rpsFractional().isEmpty() + && report.eps().isEmpty() + && report.applicationUtilization().isEmpty() + && report.namedMetrics().isEmpty(); + } + + private OrcaLoadReports() {} +} diff --git a/orca/src/test/java/com/palantir/conjure/java/api/orca/OrcaLoadReportTest.java b/orca/src/test/java/com/palantir/conjure/java/api/orca/OrcaLoadReportTest.java new file mode 100644 index 000000000..4d426050f --- /dev/null +++ b/orca/src/test/java/com/palantir/conjure/java/api/orca/OrcaLoadReportTest.java @@ -0,0 +1,74 @@ +/* + * (c) Copyright 2026 Palantir Technologies Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.palantir.conjure.java.api.orca; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +import java.util.Map; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +final class OrcaLoadReportTest { + + @Test + void defaults_to_a_report_without_metrics() { + OrcaLoadReport report = OrcaLoadReport.builder().build(); + + assertThat(report.cpuUtilization()).isEmpty(); + assertThat(report.memUtilization()).isEmpty(); + assertThat(report.rpsFractional()).isEmpty(); + assertThat(report.eps()).isEmpty(); + assertThat(report.namedMetrics()).isEmpty(); + assertThat(report.applicationUtilization()).isEmpty(); + } + + @Nested + final class Validation { + + @Test + void rejects_negative_utilization() { + assertThatIllegalArgumentException() + .isThrownBy(() -> OrcaLoadReport.builder() + .applicationUtilization(-0.1) + .build()); + } + + @Test + void allows_application_utilization_above_one() { + OrcaLoadReport report = + OrcaLoadReport.builder().applicationUtilization(1.1).build(); + + assertThat(report.applicationUtilization()).hasValue(1.1); + } + + @Test + void rejects_memory_utilization_above_one() { + assertThatIllegalArgumentException() + .isThrownBy( + () -> OrcaLoadReport.builder().memUtilization(1.1).build()); + } + + @Test + void rejects_non_finite_metrics() { + assertThatIllegalArgumentException() + .isThrownBy(() -> OrcaLoadReport.builder() + .namedMetrics(Map.of("custom", Double.NaN)) + .build()); + } + } +} diff --git a/orca/src/test/java/com/palantir/conjure/java/api/orca/OrcaLoadReportsTest.java b/orca/src/test/java/com/palantir/conjure/java/api/orca/OrcaLoadReportsTest.java new file mode 100644 index 000000000..c164fd7e7 --- /dev/null +++ b/orca/src/test/java/com/palantir/conjure/java/api/orca/OrcaLoadReportsTest.java @@ -0,0 +1,189 @@ +/* + * (c) Copyright 2026 Palantir Technologies Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.palantir.conjure.java.api.orca; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import org.junit.jupiter.api.Test; + +final class OrcaLoadReportsTest { + + @Test + void round_trips_supported_fields() { + OrcaLoadReport original = OrcaLoadReport.builder() + .cpuUtilization(0.1) + .memUtilization(0.2) + .rpsFractional(5.5) + .eps(0.6) + .applicationUtilization(0.8) + .build(); + Map headers = new HashMap<>(); + + OrcaLoadReports.encodeToResponse(original, headers, HeaderAdapter.INSTANCE); + + assertThat(OrcaLoadReports.parseFromResponse(headers, HeaderAdapter.INSTANCE)) + .contains(original); + } + + @Test + void empty_report_does_not_add_a_header() { + Map headers = new HashMap<>(); + + OrcaLoadReports.encodeToResponse(OrcaLoadReport.builder().build(), headers, HeaderAdapter.INSTANCE); + + assertThat(headers).isEmpty(); + } + + @Test + void uses_the_text_wire_format() { + Map headers = new HashMap<>(); + OrcaLoadReports.encodeToResponse( + OrcaLoadReport.builder() + .cpuUtilization(0.1) + .memUtilization(0.2) + .rpsFractional(5.5) + .eps(0.6) + .applicationUtilization(0.8) + .build(), + headers, + HeaderAdapter.INSTANCE); + + assertThat(headers) + .containsEntry( + "endpoint-load-metrics", + "TEXT cpu_utilization=0.1,mem_utilization=0.2,rps_fractional=5.5,eps=0.6," + + "application_utilization=0.8"); + } + + @Test + void parses_the_text_wire_format() { + Map headers = Map.of( + "endpoint-load-metrics", + "TEXT cpu_utilization=0.1,mem_utilization:0.2,rps_fractional=5.5,eps:0.6," + + "application_utilization=0.8"); + + assertThat(OrcaLoadReports.parseFromResponse(headers, HeaderAdapter.INSTANCE)) + .contains(OrcaLoadReport.builder() + .cpuUtilization(0.1) + .memUtilization(0.2) + .rpsFractional(5.5) + .eps(0.6) + .applicationUtilization(0.8) + .build()); + } + + @Test + void ignores_invalid_fields_without_discarding_valid_fields() { + Map headers = + Map.of("endpoint-load-metrics", "TEXT mem_utilization=1.01,application_utilization=0.8"); + + assertThat(OrcaLoadReports.parseFromResponse(headers, HeaderAdapter.INSTANCE)) + .contains(OrcaLoadReport.builder().applicationUtilization(0.8).build()); + } + + @Test + void preserves_explicit_zero_values() { + Map headers = Map.of("endpoint-load-metrics", "TEXT cpu_utilization=0.0"); + + assertThat(OrcaLoadReports.parseFromResponse(headers, HeaderAdapter.INSTANCE)) + .contains(OrcaLoadReport.builder().cpuUtilization(0.0).build()); + } + + @Test + void round_trips_named_metrics() { + OrcaLoadReport original = OrcaLoadReport.builder() + .applicationUtilization(0.8) + .namedMetrics(Map.of("queue_depth", 12.0)) + .build(); + Map headers = new HashMap<>(); + + OrcaLoadReports.encodeToResponse(original, headers, HeaderAdapter.INSTANCE); + + assertThat(headers) + .containsEntry( + "endpoint-load-metrics", "TEXT application_utilization=0.8,named_metrics.queue_depth=12.0"); + assertThat(OrcaLoadReports.parseFromResponse(headers, HeaderAdapter.INSTANCE)) + .contains(original); + } + + @Test + void percent_encodes_named_metric_keys() { + OrcaLoadReport original = OrcaLoadReport.builder() + .namedMetrics(Map.of("foo,bar=baz", 1.0)) + .build(); + Map headers = new HashMap<>(); + + OrcaLoadReports.encodeToResponse(original, headers, HeaderAdapter.INSTANCE); + + assertThat(headers).containsEntry("endpoint-load-metrics", "TEXT named_metrics.foo%2Cbar%3Dbaz=1.0"); + assertThat(OrcaLoadReports.parseFromResponse(headers, HeaderAdapter.INSTANCE)) + .contains(original); + } + + @Test + void drops_named_metrics_with_non_numeric_values() { + Map headers = + Map.of("endpoint-load-metrics", "TEXT named_metrics.ok=1.0,named_metrics.bad=oops"); + + assertThat(OrcaLoadReports.parseFromResponse(headers, HeaderAdapter.INSTANCE)) + .contains( + OrcaLoadReport.builder().namedMetrics(Map.of("ok", 1.0)).build()); + } + + @Test + void duplicate_fields_discard_the_report() { + Map headers = Map.of( + "endpoint-load-metrics", "TEXT cpu_utilization=0.1,cpu_utilization:0.2,application_utilization=0.8"); + + assertThat(OrcaLoadReports.parseFromResponse(headers, HeaderAdapter.INSTANCE)) + .isEmpty(); + } + + @Test + void missing_header_returns_empty() { + assertThat(OrcaLoadReports.parseFromResponse(Map.of(), HeaderAdapter.INSTANCE)) + .isEmpty(); + } + + @Test + void malformed_header_returns_empty() { + Map headers = Map.of("endpoint-load-metrics", "cpu_utilization=0.3"); + + assertThat(OrcaLoadReports.parseFromResponse(headers, HeaderAdapter.INSTANCE)) + .isEmpty(); + } + + private enum HeaderAdapter + implements + OrcaLoadReports.OrcaResponseEncodingAdapter>, + OrcaLoadReports.OrcaResponseDecodingAdapter> { + INSTANCE; + + @Override + public void setHeader(Map response, String headerName, String headerValue) { + response.put(headerName, headerValue); + } + + @Override + public Optional getFirstHeader(Map response, String headerName) { + return Optional.ofNullable(response.get(headerName)); + } + } +} diff --git a/settings.gradle b/settings.gradle index 21ddd731d..034a3c3d0 100644 --- a/settings.gradle +++ b/settings.gradle @@ -11,6 +11,7 @@ apply plugin: 'com.palantir.jdks.settings' rootProject.name = 'conjure-java-runtime-api' include 'errors' +include 'orca' include 'service-config' include 'ssl-config' include 'test-utils'