diff --git a/docs/src/main/sphinx/connector/prometheus.rst b/docs/src/main/sphinx/connector/prometheus.rst index 0ebc2e2f8e44..792c886859ca 100644 --- a/docs/src/main/sphinx/connector/prometheus.rst +++ b/docs/src/main/sphinx/connector/prometheus.rst @@ -58,6 +58,7 @@ Property name Description ``prometheus.auth.password`` Password for basic authentication ``prometheus.bearer.token.file`` File holding bearer token if needed for access to Prometheus ``prometheus.read-timeout`` How much time a query to Prometheus has before timing out +``prometheus.case-insensitive-name-matching`` Match Prometheus metric names case insensitively. Defaults to ``false`` ============================================= ============================================================================================ Not exhausting your Trino available heap diff --git a/plugin/trino-prometheus/pom.xml b/plugin/trino-prometheus/pom.xml index 72c1992edb8c..0a338f6df6a1 100644 --- a/plugin/trino-prometheus/pom.xml +++ b/plugin/trino-prometheus/pom.xml @@ -67,6 +67,11 @@ jackson-datatype-jsr310 + + com.google.code.findbugs + jsr305 + + com.google.guava guava diff --git a/plugin/trino-prometheus/src/main/java/io/trino/plugin/prometheus/PrometheusClient.java b/plugin/trino-prometheus/src/main/java/io/trino/plugin/prometheus/PrometheusClient.java index 0c004a141653..f721292d5175 100644 --- a/plugin/trino-prometheus/src/main/java/io/trino/plugin/prometheus/PrometheusClient.java +++ b/plugin/trino-prometheus/src/main/java/io/trino/plugin/prometheus/PrometheusClient.java @@ -29,6 +29,7 @@ import okhttp3.Request; import okhttp3.Response; +import javax.annotation.Nullable; import javax.inject.Inject; import java.io.File; @@ -41,6 +42,7 @@ import java.util.Set; import java.util.function.Supplier; +import static com.google.common.base.Verify.verify; import static com.google.common.net.HttpHeaders.AUTHORIZATION; import static io.trino.plugin.prometheus.PrometheusErrorCode.PROMETHEUS_TABLES_METRICS_RETRIEVE_ERROR; import static io.trino.plugin.prometheus.PrometheusErrorCode.PROMETHEUS_UNKNOWN_ERROR; @@ -50,6 +52,7 @@ import static io.trino.spi.type.VarcharType.VARCHAR; import static java.nio.charset.StandardCharsets.UTF_8; import static java.nio.file.Files.readString; +import static java.util.Locale.ENGLISH; import static java.util.Objects.requireNonNull; import static java.util.concurrent.TimeUnit.MILLISECONDS; @@ -61,6 +64,7 @@ public class PrometheusClient private final OkHttpClient httpClient; private final Supplier> tableSupplier; private final Type varcharMapType; + private final boolean caseInsensitiveNameMatching; @Inject public PrometheusClient(PrometheusConnectorConfig config, JsonCodec> metricCodec, TypeManager typeManager) @@ -80,6 +84,7 @@ public PrometheusClient(PrometheusConnectorConfig config, JsonCodec getTableNames(String schema) throw new TrinoException(PROMETHEUS_TABLES_METRICS_RETRIEVE_ERROR, "Prometheus did no return metrics list (table names): " + status); } + @Nullable public PrometheusTable getTable(String schema, String tableName) { requireNonNull(schema, "schema is null"); @@ -114,21 +120,43 @@ public PrometheusTable getTable(String schema, String tableName) return null; } - List tableNames = (List) tableSupplier.get().get("data"); - if (tableNames == null) { - return null; - } - if (!tableNames.contains(tableName)) { + String remoteTableName = toRemoteTableName(tableName); + if (remoteTableName == null) { return null; } return new PrometheusTable( - tableName, + remoteTableName, ImmutableList.of( new PrometheusColumn("labels", varcharMapType), new PrometheusColumn("timestamp", TIMESTAMP_COLUMN_TYPE), new PrometheusColumn("value", DoubleType.DOUBLE))); } + @Nullable + private String toRemoteTableName(String tableName) + { + verify(tableName.equals(tableName.toLowerCase(ENGLISH)), "tableName not in lower-case: %s", tableName); + List tableNames = (List) tableSupplier.get().get("data"); + if (tableNames == null) { + return null; + } + + if (!caseInsensitiveNameMatching) { + if (tableNames.contains(tableName)) { + return tableName; + } + } + else { + for (String remoteTableName : tableNames) { + if (tableName.equals(remoteTableName.toLowerCase(ENGLISH))) { + return remoteTableName; + } + } + } + + return null; + } + private Map fetchMetrics(JsonCodec> metricsCodec, URI metadataUri) { return metricsCodec.fromJson(fetchUri(metadataUri)); diff --git a/plugin/trino-prometheus/src/main/java/io/trino/plugin/prometheus/PrometheusConnectorConfig.java b/plugin/trino-prometheus/src/main/java/io/trino/plugin/prometheus/PrometheusConnectorConfig.java index 9f217ed9d81b..6ed34452f926 100644 --- a/plugin/trino-prometheus/src/main/java/io/trino/plugin/prometheus/PrometheusConnectorConfig.java +++ b/plugin/trino-prometheus/src/main/java/io/trino/plugin/prometheus/PrometheusConnectorConfig.java @@ -40,6 +40,7 @@ public class PrometheusConnectorConfig private File bearerTokenFile; private String user; private String password; + private boolean caseInsensitiveNameMatching; @NotNull public URI getPrometheusURI() @@ -151,6 +152,19 @@ public PrometheusConnectorConfig setReadTimeout(Duration readTimeout) return this; } + public boolean isCaseInsensitiveNameMatching() + { + return caseInsensitiveNameMatching; + } + + @Config("prometheus.case-insensitive-name-matching") + @ConfigDescription("Where to match the prometheus metric name case insensitively ") + public PrometheusConnectorConfig setCaseInsensitiveNameMatching(boolean caseInsensitiveNameMatching) + { + this.caseInsensitiveNameMatching = caseInsensitiveNameMatching; + return this; + } + @PostConstruct public void checkConfig() { diff --git a/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusCaseInsensitiveNameMatching.java b/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusCaseInsensitiveNameMatching.java new file mode 100644 index 000000000000..8bc744306441 --- /dev/null +++ b/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusCaseInsensitiveNameMatching.java @@ -0,0 +1,96 @@ +/* + * 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 io.trino.plugin.prometheus; + +import io.trino.spi.connector.SchemaTableName; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; +import org.testng.annotations.Test; + +import java.util.List; +import java.util.Optional; +import java.util.Set; + +import static io.trino.plugin.prometheus.MetadataUtil.METRIC_CODEC; +import static io.trino.type.InternalTypeManager.TESTING_TYPE_MANAGER; +import static java.util.Locale.ENGLISH; +import static org.assertj.core.api.Assertions.assertThat; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertNull; +import static org.testng.Assert.assertTrue; + +public class TestPrometheusCaseInsensitiveNameMatching +{ + private PrometheusHttpServer prometheusHttpServer; + private static final String DEFAULT_SCHEMA = "default"; + private static final String UPPER_CASE_METRIC = "UpperCase-Metric"; + + @BeforeClass + public void setUp() + { + prometheusHttpServer = new PrometheusHttpServer(); + } + + @AfterClass(alwaysRun = true) + public void tearDown() + { + if (prometheusHttpServer != null) { + prometheusHttpServer.stop(); + } + } + + @Test + public void testCaseInsensitiveNameMatchingFalse() + { + PrometheusConnectorConfig config = new PrometheusConnectorConfig(); + config.setPrometheusURI(prometheusHttpServer.resolve("/prometheus-data/uppercase-metrics.json")); + + PrometheusClient client = new PrometheusClient(config, METRIC_CODEC, TESTING_TYPE_MANAGER); + + Set tableNames = client.getTableNames(DEFAULT_SCHEMA); + assertThat(tableNames).hasSize(1); + assertTrue(tableNames.contains(UPPER_CASE_METRIC)); + + PrometheusMetadata metadata = new PrometheusMetadata(client); + List tables = metadata.listTables(null, Optional.of(DEFAULT_SCHEMA)); + assertThat(tableNames).hasSize(1); + assertEquals(UPPER_CASE_METRIC.toLowerCase(ENGLISH), tables.get(0).getTableName()); + + assertNull(client.getTable(DEFAULT_SCHEMA, tables.get(0).getTableName())); + } + + @Test + public void testCaseInsensitiveNameMatchingTrue() + { + PrometheusConnectorConfig config = new PrometheusConnectorConfig(); + config.setPrometheusURI(prometheusHttpServer.resolve("/prometheus-data/uppercase-metrics.json")); + config.setCaseInsensitiveNameMatching(true); + + PrometheusClient client = new PrometheusClient(config, METRIC_CODEC, TESTING_TYPE_MANAGER); + + Set tableNames = client.getTableNames(DEFAULT_SCHEMA); + assertThat(tableNames).hasSize(1); + assertTrue(tableNames.contains(UPPER_CASE_METRIC)); + + PrometheusMetadata metadata = new PrometheusMetadata(client); + List tables = metadata.listTables(null, Optional.of(DEFAULT_SCHEMA)); + assertThat(tableNames).hasSize(1); + SchemaTableName table = tables.get(0); + assertEquals(UPPER_CASE_METRIC.toLowerCase(ENGLISH), table.getTableName()); + + assertNotNull(client.getTable(DEFAULT_SCHEMA, tables.get(0).getTableName())); + assertEquals(UPPER_CASE_METRIC, client.getTable(DEFAULT_SCHEMA, tables.get(0).getTableName()).getName()); + } +} diff --git a/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusConnectorConfig.java b/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusConnectorConfig.java index 3dd66661f12d..bde3c56764a1 100644 --- a/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusConnectorConfig.java +++ b/plugin/trino-prometheus/src/test/java/io/trino/plugin/prometheus/TestPrometheusConnectorConfig.java @@ -44,7 +44,8 @@ public void testDefaults() .setBearerTokenFile(null) .setUser(null) .setPassword(null) - .setReadTimeout(new Duration(10, SECONDS))); + .setReadTimeout(new Duration(10, SECONDS)) + .setCaseInsensitiveNameMatching(false)); } @Test @@ -59,6 +60,7 @@ public void testExplicitPropertyMappings() .put("prometheus.auth.user", "admin") .put("prometheus.auth.password", "password") .put("prometheus.read-timeout", "30s") + .put("prometheus.case-insensitive-name-matching", "true") .buildOrThrow(); URI uri = URI.create("file://test.json"); @@ -71,6 +73,7 @@ public void testExplicitPropertyMappings() expected.setUser("admin"); expected.setPassword("password"); expected.setReadTimeout(new Duration(30, SECONDS)); + expected.setCaseInsensitiveNameMatching(true); assertFullMapping(properties, expected); } diff --git a/plugin/trino-prometheus/src/test/resources/prometheus-data/uppercase-metrics.json b/plugin/trino-prometheus/src/test/resources/prometheus-data/uppercase-metrics.json new file mode 100644 index 000000000000..94f17646e67a --- /dev/null +++ b/plugin/trino-prometheus/src/test/resources/prometheus-data/uppercase-metrics.json @@ -0,0 +1,6 @@ +{ + "status": "success", + "data": [ + "UpperCase-Metric" + ] +}