From 0ef8232da92adf513ad2542c7fad1791d10712a3 Mon Sep 17 00:00:00 2001 From: Mary Gouseti Date: Thu, 28 Sep 2023 11:45:32 +0300 Subject: [PATCH 01/32] Replace 200_logs_datastream_defaults.yml with java rest test (#99979) --- .../datastreams/LogsDataStreamIT.java | 169 ++++++++++++++++++ .../200_logs_datastream_defaults.yml | 108 ----------- 2 files changed, 169 insertions(+), 108 deletions(-) create mode 100644 modules/data-streams/src/javaRestTest/java/org/elasticsearch/datastreams/LogsDataStreamIT.java delete mode 100644 modules/data-streams/src/yamlRestTest/resources/rest-api-spec/test/data_stream/200_logs_datastream_defaults.yml diff --git a/modules/data-streams/src/javaRestTest/java/org/elasticsearch/datastreams/LogsDataStreamIT.java b/modules/data-streams/src/javaRestTest/java/org/elasticsearch/datastreams/LogsDataStreamIT.java new file mode 100644 index 0000000000000..31d2f6a8e2171 --- /dev/null +++ b/modules/data-streams/src/javaRestTest/java/org/elasticsearch/datastreams/LogsDataStreamIT.java @@ -0,0 +1,169 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.datastreams; + +import org.elasticsearch.client.Request; +import org.elasticsearch.client.ResponseException; +import org.elasticsearch.client.RestClient; +import org.junit.After; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.is; + +public class LogsDataStreamIT extends DisabledSecurityDataStreamTestCase { + + @After + public void cleanUp() throws IOException { + adminClient().performRequest(new Request("DELETE", "_data_stream/*")); + } + + @SuppressWarnings("unchecked") + public void testDefaultLogsSettingAndMapping() throws Exception { + RestClient client = client(); + waitForLogs(client); + + String dataStreamName = "logs-generic-default"; + createDataStream(client, dataStreamName); + String backingIndex = getWriteBackingIndex(client, dataStreamName); + + // Ensure correct settings + Map settings = getSettings(client, backingIndex); + assertThat(settings.get("index.mapping.ignore_malformed"), is("true")); + + // Extend the mapping and verify + putMapping(client, backingIndex); + Map mappingProperties = getMappingProperties(client, backingIndex); + assertThat(((Map) mappingProperties.get("@timestamp")).get("ignore_malformed"), equalTo(false)); + assertThat(((Map) mappingProperties.get("numeric_field")).get("type"), equalTo("integer")); + + // Insert valid doc and verify successful indexing + { + indexDoc(client, dataStreamName, """ + { + "@timestamp": "2023-04-18", + "message": "valid", + "numeric_field": 42 + } + """); + List results = searchDocs(client, dataStreamName, """ + { + "query": { + "term": { + "message": { + "value": "valid" + } + } + }, + "fields": ["numeric_field"] + } + """); + Map fields = ((Map>) results.get(0)).get("fields"); + assertThat(fields.get("numeric_field"), is(List.of(42))); + } + + // Insert invalid doc and verify successful indexing + { + indexDoc(client, dataStreamName, """ + { + "@timestamp": "2023-04-18", + "message": "invalid", + "numeric_field": "forty-two" + } + """); + List results = searchDocs(client, dataStreamName, """ + { + "query": { + "term": { + "message": { + "value": "invalid" + } + } + }, + "fields": ["numeric_field"] + } + """); + List ignored = ((Map>) results.get(0)).get("_ignored"); + assertThat(ignored, contains("numeric_field")); + Map ignoredFieldValues = ((Map>) results.get(0)).get("ignored_field_values"); + assertThat(ignoredFieldValues.get("numeric_field"), is(List.of("forty-two"))); + } + } + + private static void waitForLogs(RestClient client) throws Exception { + assertBusy(() -> { + try { + Request request = new Request("GET", "_index_template/logs"); + assertOK(client.performRequest(request)); + } catch (ResponseException e) { + fail(e.getMessage()); + } + }); + } + + private static void createDataStream(RestClient client, String name) throws IOException { + Request request = new Request("PUT", "_data_stream/" + name); + assertOK(client.performRequest(request)); + } + + @SuppressWarnings("unchecked") + private static String getWriteBackingIndex(RestClient client, String name) throws IOException { + Request request = new Request("GET", "_data_stream/" + name); + List dataStreams = (List) entityAsMap(client.performRequest(request)).get("data_streams"); + Map dataStream = (Map) dataStreams.get(0); + List> indices = (List>) dataStream.get("indices"); + return indices.get(0).get("index_name"); + } + + @SuppressWarnings("unchecked") + private static Map getSettings(RestClient client, String indexName) throws IOException { + Request request = new Request("GET", "/" + indexName + "/_settings?flat_settings"); + return ((Map>) entityAsMap(client.performRequest(request)).get(indexName)).get("settings"); + } + + private static void putMapping(RestClient client, String indexName) throws IOException { + Request request = new Request("PUT", "/" + indexName + "/_mapping"); + request.setJsonEntity(""" + { + "properties": { + "numeric_field": { + "type": "integer" + } + } + } + """); + assertOK(client.performRequest(request)); + } + + @SuppressWarnings("unchecked") + private static Map getMappingProperties(RestClient client, String indexName) throws IOException { + Request request = new Request("GET", "/" + indexName + "/_mapping"); + Map map = (Map) entityAsMap(client.performRequest(request)).get(indexName); + Map mappings = (Map) map.get("mappings"); + return (Map) mappings.get("properties"); + } + + private static void indexDoc(RestClient client, String dataStreamName, String doc) throws IOException { + Request request = new Request("POST", "/" + dataStreamName + "/_doc?refresh=true"); + request.setJsonEntity(doc); + assertOK(client.performRequest(request)); + } + + @SuppressWarnings("unchecked") + private static List searchDocs(RestClient client, String dataStreamName, String query) throws IOException { + Request request = new Request("GET", "/" + dataStreamName + "/_search"); + request.setJsonEntity(query); + Map hits = (Map) entityAsMap(client.performRequest(request)).get("hits"); + return (List) hits.get("hits"); + } +} diff --git a/modules/data-streams/src/yamlRestTest/resources/rest-api-spec/test/data_stream/200_logs_datastream_defaults.yml b/modules/data-streams/src/yamlRestTest/resources/rest-api-spec/test/data_stream/200_logs_datastream_defaults.yml deleted file mode 100644 index eaaf0893c1a83..0000000000000 --- a/modules/data-streams/src/yamlRestTest/resources/rest-api-spec/test/data_stream/200_logs_datastream_defaults.yml +++ /dev/null @@ -1,108 +0,0 @@ ---- -Verify default logs-*-* settings and mappings: - - do: - indices.create_data_stream: - name: logs-generic-default - - is_true: acknowledged - - - do: - indices.get_data_stream: - name: logs-generic-default - - set: { data_streams.0.indices.0.index_name: idx0name } - - # default backing index settings should be "ignore_malformed": true - - do: - indices.get_settings: - index: $idx0name - - match: { .$idx0name.settings.index.mapping.ignore_malformed: "true" } - - # add test field mapping - - do: - indices.put_mapping: - index: $idx0name - body: - properties: - numeric_field: - type: integer - - is_true: acknowledged - - # default backing index mapping should contain an exception for the @timestamp field - "ignore_malformed": false - - do: - indices.get_mapping: - index: $idx0name - - match: { .$idx0name.mappings.properties.@timestamp.ignore_malformed: false } - - match: { .$idx0name.mappings.properties.numeric_field.type: "integer" } - - - do: - index: - index: logs-generic-default - refresh: true - body: - '@timestamp': '2023-04-18' - message: 'valid' - numeric_field: 42 - - match: {result: "created"} - - - do: - search: - index: logs-generic-default - body: - query: - term: - message: - value: 'valid' - fields: - - field: 'numeric_field' - - length: { hits.hits: 1 } - - length: { hits.hits.0.fields: 1 } - - match: { hits.hits.0.fields.numeric_field.0: 42 } - - - do: - index: - index: logs-generic-default - refresh: true - body: - '@timestamp': '2023-04-18' - message: 'number_as_string' - numeric_field: "42" - - match: {result: "created"} - - - do: - search: - index: logs-generic-default - body: - query: - term: - message: - value: 'number_as_string' - fields: - - field: 'numeric_field' - - length: { hits.hits: 1 } - - length: { hits.hits.0.fields: 1 } - - match: { hits.hits.0.fields.numeric_field.0: 42 } - - - do: - index: - index: logs-generic-default - refresh: true - body: - '@timestamp': '2023-04-18' - message: 'invalid' - numeric_field: "forty-two" - - match: {result: "created"} - - - do: - search: - index: logs-generic-default - body: - query: - term: - message: - value: 'invalid' - fields: - - field: 'numeric_field' - - length: { hits.hits: 1 } - - length: { hits.hits.0._ignored: 1 } - - match: { hits.hits.0._ignored.0: 'numeric_field' } - - length: { hits.hits.0.ignored_field_values.numeric_field: 1 } - - match: { hits.hits.0.ignored_field_values.numeric_field.0: 'forty-two' } From d106fece41feb58c5f224c9eb52ec90f2de7755a Mon Sep 17 00:00:00 2001 From: Mary Gouseti Date: Thu, 28 Sep 2023 11:45:51 +0300 Subject: [PATCH 02/32] Replace the disabled stack templates yaml test with java rest test (#99978) --- .../xpack/core/StackTemplatesRestIT.java | 59 +++++++++++++++++++ .../rest-api-spec/test/stack/10_stack.yml | 22 ------- 2 files changed, 59 insertions(+), 22 deletions(-) create mode 100644 x-pack/plugin/core/src/javaRestTest/java/org/elasticsearch/xpack/core/StackTemplatesRestIT.java delete mode 100644 x-pack/qa/core-rest-tests-with-security/src/yamlRestTest/resources/rest-api-spec/test/stack/10_stack.yml diff --git a/x-pack/plugin/core/src/javaRestTest/java/org/elasticsearch/xpack/core/StackTemplatesRestIT.java b/x-pack/plugin/core/src/javaRestTest/java/org/elasticsearch/xpack/core/StackTemplatesRestIT.java new file mode 100644 index 0000000000000..fcbf955c2b9ae --- /dev/null +++ b/x-pack/plugin/core/src/javaRestTest/java/org/elasticsearch/xpack/core/StackTemplatesRestIT.java @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ +package org.elasticsearch.xpack.core; + +import org.elasticsearch.client.Request; +import org.elasticsearch.client.ResponseException; +import org.elasticsearch.client.RestClient; +import org.elasticsearch.common.settings.SecureString; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.util.concurrent.ThreadContext; +import org.elasticsearch.test.rest.ESRestTestCase; + +import static org.hamcrest.Matchers.is; + +public class StackTemplatesRestIT extends ESRestTestCase { + + private static final String BASIC_AUTH_VALUE = basicAuthHeaderValue("x_pack_rest_user", new SecureString("x-pack-test-password")); + + @Override + protected Settings restClientSettings() { + return Settings.builder().put(ThreadContext.PREFIX + ".Authorization", BASIC_AUTH_VALUE).build(); + } + + public void testTemplatesCanBeDisabled() throws Exception { + RestClient client = client(); + // Ensure the logs template has been added + assertBusy(() -> { + try { + Request request = new Request("GET", "_index_template/logs"); + assertOK(client.performRequest(request)); + } catch (ResponseException e) { + fail(e.getMessage()); + } + }); + // Disable the stack templates + { + Request request = new Request("PUT", "_cluster/settings"); + request.setJsonEntity(""" + { + "persistent": { + "stack.templates.enabled": false + } + } + """); + assertOK(client.performRequest(request)); + } + Request getRequest = new Request("GET", "_index_template/logs"); + assertOK(client.performRequest(getRequest)); + + Request deleteRequest = new Request("DELETE", "_index_template/logs"); + assertOK(client.performRequest(deleteRequest)); + ResponseException exception = expectThrows(ResponseException.class, () -> client.performRequest(deleteRequest)); + assertThat(exception.getResponse().getStatusLine().getStatusCode(), is(404)); + } +} diff --git a/x-pack/qa/core-rest-tests-with-security/src/yamlRestTest/resources/rest-api-spec/test/stack/10_stack.yml b/x-pack/qa/core-rest-tests-with-security/src/yamlRestTest/resources/rest-api-spec/test/stack/10_stack.yml deleted file mode 100644 index 3346f5c8e58bd..0000000000000 --- a/x-pack/qa/core-rest-tests-with-security/src/yamlRestTest/resources/rest-api-spec/test/stack/10_stack.yml +++ /dev/null @@ -1,22 +0,0 @@ -"Stack templates can be disabled": - - skip: - version: all - reason: https://github.com/elastic/elasticsearch/issues/98163 - - do: - cluster.put_settings: - body: - persistent: - stack.templates.enabled: false - - - do: - indices.get_index_template: - name: logs - - - do: - indices.delete_index_template: - name: logs - - - do: - catch: missing - indices.get_index_template: - name: logs From a698f4dce22f95ff234ff67ca04a3905fd9906a5 Mon Sep 17 00:00:00 2001 From: Jaime Pan <33685703+NEUpanning@users.noreply.github.com> Date: Thu, 28 Sep 2023 16:54:55 +0800 Subject: [PATCH 03/32] Extract NodeInfoMetrics to top-level class (#99990) --- .../cluster/node/info/NodesInfoMetrics.java | 75 +++++++++++++++++++ .../cluster/node/info/NodesInfoRequest.java | 66 +++++----------- .../node/info/NodesInfoRequestBuilder.java | 22 +++--- .../node/info/TransportNodesInfoAction.java | 24 +++--- .../remote/RemoteClusterNodesAction.java | 3 +- .../ingest/PutPipelineTransportAction.java | 3 +- .../admin/cluster/RestNodesInfoAction.java | 3 +- .../rest/action/cat/RestNodeAttrsAction.java | 3 +- .../rest/action/cat/RestNodesAction.java | 9 ++- .../rest/action/cat/RestPluginsAction.java | 3 +- .../rest/action/cat/RestThreadPoolAction.java | 3 +- .../node/info/NodesInfoRequestTests.java | 12 +-- .../remote/RemoteClusterNodesActionTests.java | 3 +- .../nodeinfo/AutoscalingNodeInfoService.java | 3 +- .../TransportNodeEnrollmentAction.java | 3 +- .../InternalEnrollmentTokenGenerator.java | 5 +- 16 files changed, 147 insertions(+), 93 deletions(-) create mode 100644 server/src/main/java/org/elasticsearch/action/admin/cluster/node/info/NodesInfoMetrics.java diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/node/info/NodesInfoMetrics.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/node/info/NodesInfoMetrics.java new file mode 100644 index 0000000000000..3e632f9bdd212 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/node/info/NodesInfoMetrics.java @@ -0,0 +1,75 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.action.admin.cluster.node.info; + +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.io.stream.Writeable; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * This class is a container that encapsulates the necessary information needed to indicate which node information is requested. + */ +public class NodesInfoMetrics implements Writeable { + private Set requestedMetrics = Metric.allMetrics(); + + public NodesInfoMetrics() {} + + public NodesInfoMetrics(StreamInput in) throws IOException { + requestedMetrics.clear(); + requestedMetrics.addAll(Arrays.asList(in.readStringArray())); + } + + public Set requestedMetrics() { + return requestedMetrics; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeStringArray(requestedMetrics.toArray(new String[0])); + } + + /** + * An enumeration of the "core" sections of metrics that may be requested + * from the nodes information endpoint. Eventually this list will be + * pluggable. + */ + public enum Metric { + SETTINGS("settings"), + OS("os"), + PROCESS("process"), + JVM("jvm"), + THREAD_POOL("thread_pool"), + TRANSPORT("transport"), + HTTP("http"), + REMOTE_CLUSTER_SERVER("remote_cluster_server"), + PLUGINS("plugins"), + INGEST("ingest"), + AGGREGATIONS("aggregations"), + INDICES("indices"); + + private final String metricName; + + Metric(String name) { + this.metricName = name; + } + + public String metricName() { + return this.metricName; + } + + public static Set allMetrics() { + return Arrays.stream(values()).map(Metric::metricName).collect(Collectors.toSet()); + } + } +} diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/node/info/NodesInfoRequest.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/node/info/NodesInfoRequest.java index 4e52116020be2..04b2a7d980678 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/node/info/NodesInfoRequest.java +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/node/info/NodesInfoRequest.java @@ -13,18 +13,16 @@ import org.elasticsearch.common.io.stream.StreamOutput; import java.io.IOException; -import java.util.Arrays; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; -import java.util.stream.Collectors; /** * A request to get node (cluster) level information. */ public class NodesInfoRequest extends BaseNodesRequest { - private Set requestedMetrics = Metric.allMetrics(); + private NodesInfoMetrics nodesInfoMetrics; /** * Create a new NodeInfoRequest from a {@link StreamInput} object. @@ -34,8 +32,7 @@ public class NodesInfoRequest extends BaseNodesRequest { */ public NodesInfoRequest(StreamInput in) throws IOException { super(in); - requestedMetrics.clear(); - requestedMetrics.addAll(Arrays.asList(in.readStringArray())); + nodesInfoMetrics = new NodesInfoMetrics(in); } /** @@ -45,6 +42,7 @@ public NodesInfoRequest(StreamInput in) throws IOException { @SuppressWarnings("this-escape") public NodesInfoRequest(String... nodesIds) { super(nodesIds); + nodesInfoMetrics = new NodesInfoMetrics(); all(); } @@ -52,7 +50,7 @@ public NodesInfoRequest(String... nodesIds) { * Clears all info flags. */ public NodesInfoRequest clear() { - requestedMetrics.clear(); + nodesInfoMetrics.requestedMetrics().clear(); return this; } @@ -60,7 +58,7 @@ public NodesInfoRequest clear() { * Sets to return all the data. */ public NodesInfoRequest all() { - requestedMetrics.addAll(Metric.allMetrics()); + nodesInfoMetrics.requestedMetrics().addAll(NodesInfoMetrics.Metric.allMetrics()); return this; } @@ -68,17 +66,17 @@ public NodesInfoRequest all() { * Get the names of requested metrics */ public Set requestedMetrics() { - return Set.copyOf(requestedMetrics); + return Set.copyOf(nodesInfoMetrics.requestedMetrics()); } /** * Add metric */ public NodesInfoRequest addMetric(String metric) { - if (Metric.allMetrics().contains(metric) == false) { + if (NodesInfoMetrics.Metric.allMetrics().contains(metric) == false) { throw new IllegalStateException("Used an illegal metric: " + metric); } - requestedMetrics.add(metric); + nodesInfoMetrics.requestedMetrics().add(metric); return this; } @@ -87,12 +85,12 @@ public NodesInfoRequest addMetric(String metric) { */ public NodesInfoRequest addMetrics(String... metrics) { SortedSet metricsSet = new TreeSet<>(Set.of(metrics)); - if (Metric.allMetrics().containsAll(metricsSet) == false) { - metricsSet.removeAll(Metric.allMetrics()); + if (NodesInfoMetrics.Metric.allMetrics().containsAll(metricsSet) == false) { + metricsSet.removeAll(NodesInfoMetrics.Metric.allMetrics()); String plural = metricsSet.size() == 1 ? "" : "s"; throw new IllegalStateException("Used illegal metric" + plural + ": " + metricsSet); } - requestedMetrics.addAll(metricsSet); + nodesInfoMetrics.requestedMetrics().addAll(metricsSet); return this; } @@ -100,17 +98,17 @@ public NodesInfoRequest addMetrics(String... metrics) { * Remove metric */ public NodesInfoRequest removeMetric(String metric) { - if (Metric.allMetrics().contains(metric) == false) { + if (NodesInfoMetrics.Metric.allMetrics().contains(metric) == false) { throw new IllegalStateException("Used an illegal metric: " + metric); } - requestedMetrics.remove(metric); + nodesInfoMetrics.requestedMetrics().remove(metric); return this; } @Override public void writeTo(StreamOutput out) throws IOException { super.writeTo(out); - out.writeStringCollection(requestedMetrics); + nodesInfoMetrics.writeTo(out); } /** @@ -118,7 +116,7 @@ public void writeTo(StreamOutput out) throws IOException { * @param metrics the metrics to include in the request * @return */ - public static NodesInfoRequest requestWithMetrics(Metric... metrics) { + public static NodesInfoRequest requestWithMetrics(NodesInfoMetrics.Metric... metrics) { NodesInfoRequest nodesInfoRequest = new NodesInfoRequest(); nodesInfoRequest.clear(); for (var metric : metrics) { @@ -127,37 +125,7 @@ public static NodesInfoRequest requestWithMetrics(Metric... metrics) { return nodesInfoRequest; } - /** - * An enumeration of the "core" sections of metrics that may be requested - * from the nodes information endpoint. Eventually this list list will be - * pluggable. - */ - public enum Metric { - SETTINGS("settings"), - OS("os"), - PROCESS("process"), - JVM("jvm"), - THREAD_POOL("thread_pool"), - TRANSPORT("transport"), - HTTP("http"), - REMOTE_CLUSTER_SERVER("remote_cluster_server"), - PLUGINS("plugins"), - INGEST("ingest"), - AGGREGATIONS("aggregations"), - INDICES("indices"); - - private final String metricName; - - Metric(String name) { - this.metricName = name; - } - - public String metricName() { - return this.metricName; - } - - public static Set allMetrics() { - return Arrays.stream(values()).map(Metric::metricName).collect(Collectors.toSet()); - } + public NodesInfoMetrics getNodesInfoMetrics() { + return nodesInfoMetrics; } } diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/node/info/NodesInfoRequestBuilder.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/node/info/NodesInfoRequestBuilder.java index e839553021c3e..543804869eaa0 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/node/info/NodesInfoRequestBuilder.java +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/node/info/NodesInfoRequestBuilder.java @@ -38,7 +38,7 @@ public NodesInfoRequestBuilder all() { * Should the node settings be returned. */ public NodesInfoRequestBuilder setSettings(boolean settings) { - addOrRemoveMetric(settings, NodesInfoRequest.Metric.SETTINGS); + addOrRemoveMetric(settings, NodesInfoMetrics.Metric.SETTINGS); return this; } @@ -46,7 +46,7 @@ public NodesInfoRequestBuilder setSettings(boolean settings) { * Should the node OS info be returned. */ public NodesInfoRequestBuilder setOs(boolean os) { - addOrRemoveMetric(os, NodesInfoRequest.Metric.OS); + addOrRemoveMetric(os, NodesInfoMetrics.Metric.OS); return this; } @@ -54,7 +54,7 @@ public NodesInfoRequestBuilder setOs(boolean os) { * Should the node OS process be returned. */ public NodesInfoRequestBuilder setProcess(boolean process) { - addOrRemoveMetric(process, NodesInfoRequest.Metric.PROCESS); + addOrRemoveMetric(process, NodesInfoMetrics.Metric.PROCESS); return this; } @@ -62,7 +62,7 @@ public NodesInfoRequestBuilder setProcess(boolean process) { * Should the node JVM info be returned. */ public NodesInfoRequestBuilder setJvm(boolean jvm) { - addOrRemoveMetric(jvm, NodesInfoRequest.Metric.JVM); + addOrRemoveMetric(jvm, NodesInfoMetrics.Metric.JVM); return this; } @@ -70,7 +70,7 @@ public NodesInfoRequestBuilder setJvm(boolean jvm) { * Should the node thread pool info be returned. */ public NodesInfoRequestBuilder setThreadPool(boolean threadPool) { - addOrRemoveMetric(threadPool, NodesInfoRequest.Metric.THREAD_POOL); + addOrRemoveMetric(threadPool, NodesInfoMetrics.Metric.THREAD_POOL); return this; } @@ -78,7 +78,7 @@ public NodesInfoRequestBuilder setThreadPool(boolean threadPool) { * Should the node Transport info be returned. */ public NodesInfoRequestBuilder setTransport(boolean transport) { - addOrRemoveMetric(transport, NodesInfoRequest.Metric.TRANSPORT); + addOrRemoveMetric(transport, NodesInfoMetrics.Metric.TRANSPORT); return this; } @@ -86,7 +86,7 @@ public NodesInfoRequestBuilder setTransport(boolean transport) { * Should the node HTTP info be returned. */ public NodesInfoRequestBuilder setHttp(boolean http) { - addOrRemoveMetric(http, NodesInfoRequest.Metric.HTTP); + addOrRemoveMetric(http, NodesInfoMetrics.Metric.HTTP); return this; } @@ -94,7 +94,7 @@ public NodesInfoRequestBuilder setHttp(boolean http) { * Should the node plugins info be returned. */ public NodesInfoRequestBuilder setPlugins(boolean plugins) { - addOrRemoveMetric(plugins, NodesInfoRequest.Metric.PLUGINS); + addOrRemoveMetric(plugins, NodesInfoMetrics.Metric.PLUGINS); return this; } @@ -102,7 +102,7 @@ public NodesInfoRequestBuilder setPlugins(boolean plugins) { * Should the node ingest info be returned. */ public NodesInfoRequestBuilder setIngest(boolean ingest) { - addOrRemoveMetric(ingest, NodesInfoRequest.Metric.INGEST); + addOrRemoveMetric(ingest, NodesInfoMetrics.Metric.INGEST); return this; } @@ -110,11 +110,11 @@ public NodesInfoRequestBuilder setIngest(boolean ingest) { * Should the node indices info be returned. */ public NodesInfoRequestBuilder setIndices(boolean indices) { - addOrRemoveMetric(indices, NodesInfoRequest.Metric.INDICES); + addOrRemoveMetric(indices, NodesInfoMetrics.Metric.INDICES); return this; } - private void addOrRemoveMetric(boolean includeMetric, NodesInfoRequest.Metric metric) { + private void addOrRemoveMetric(boolean includeMetric, NodesInfoMetrics.Metric metric) { if (includeMetric) { request.addMetric(metric.metricName()); } else { diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/node/info/TransportNodesInfoAction.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/node/info/TransportNodesInfoAction.java index ccd6ccde2dabd..14dffce86daa5 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/node/info/TransportNodesInfoAction.java +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/node/info/TransportNodesInfoAction.java @@ -79,18 +79,18 @@ protected NodeInfo nodeOperation(NodeInfoRequest nodeRequest, Task task) { NodesInfoRequest request = nodeRequest.request; Set metrics = request.requestedMetrics(); return nodeService.info( - metrics.contains(NodesInfoRequest.Metric.SETTINGS.metricName()), - metrics.contains(NodesInfoRequest.Metric.OS.metricName()), - metrics.contains(NodesInfoRequest.Metric.PROCESS.metricName()), - metrics.contains(NodesInfoRequest.Metric.JVM.metricName()), - metrics.contains(NodesInfoRequest.Metric.THREAD_POOL.metricName()), - metrics.contains(NodesInfoRequest.Metric.TRANSPORT.metricName()), - metrics.contains(NodesInfoRequest.Metric.HTTP.metricName()), - metrics.contains(NodesInfoRequest.Metric.REMOTE_CLUSTER_SERVER.metricName()), - metrics.contains(NodesInfoRequest.Metric.PLUGINS.metricName()), - metrics.contains(NodesInfoRequest.Metric.INGEST.metricName()), - metrics.contains(NodesInfoRequest.Metric.AGGREGATIONS.metricName()), - metrics.contains(NodesInfoRequest.Metric.INDICES.metricName()) + metrics.contains(NodesInfoMetrics.Metric.SETTINGS.metricName()), + metrics.contains(NodesInfoMetrics.Metric.OS.metricName()), + metrics.contains(NodesInfoMetrics.Metric.PROCESS.metricName()), + metrics.contains(NodesInfoMetrics.Metric.JVM.metricName()), + metrics.contains(NodesInfoMetrics.Metric.THREAD_POOL.metricName()), + metrics.contains(NodesInfoMetrics.Metric.TRANSPORT.metricName()), + metrics.contains(NodesInfoMetrics.Metric.HTTP.metricName()), + metrics.contains(NodesInfoMetrics.Metric.REMOTE_CLUSTER_SERVER.metricName()), + metrics.contains(NodesInfoMetrics.Metric.PLUGINS.metricName()), + metrics.contains(NodesInfoMetrics.Metric.INGEST.metricName()), + metrics.contains(NodesInfoMetrics.Metric.AGGREGATIONS.metricName()), + metrics.contains(NodesInfoMetrics.Metric.INDICES.metricName()) ); } diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/remote/RemoteClusterNodesAction.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/remote/RemoteClusterNodesAction.java index 5898f258865d7..49d1dde59477b 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/remote/RemoteClusterNodesAction.java +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/remote/RemoteClusterNodesAction.java @@ -15,6 +15,7 @@ import org.elasticsearch.action.ActionResponse; import org.elasticsearch.action.ActionType; import org.elasticsearch.action.admin.cluster.node.info.NodesInfoAction; +import org.elasticsearch.action.admin.cluster.node.info.NodesInfoMetrics; import org.elasticsearch.action.admin.cluster.node.info.NodesInfoRequest; import org.elasticsearch.action.admin.cluster.node.info.NodesInfoResponse; import org.elasticsearch.action.support.ActionFilters; @@ -108,7 +109,7 @@ protected void doExecute(Task task, Request request, ActionListener li threadContext.markAsSystemContext(); if (request.remoteClusterServer) { final NodesInfoRequest nodesInfoRequest = new NodesInfoRequest().clear() - .addMetrics(NodesInfoRequest.Metric.REMOTE_CLUSTER_SERVER.metricName()); + .addMetrics(NodesInfoMetrics.Metric.REMOTE_CLUSTER_SERVER.metricName()); transportService.sendRequest( transportService.getLocalNode(), NodesInfoAction.NAME, diff --git a/server/src/main/java/org/elasticsearch/action/ingest/PutPipelineTransportAction.java b/server/src/main/java/org/elasticsearch/action/ingest/PutPipelineTransportAction.java index 65d5ad5807ecb..b1e2533d1038d 100644 --- a/server/src/main/java/org/elasticsearch/action/ingest/PutPipelineTransportAction.java +++ b/server/src/main/java/org/elasticsearch/action/ingest/PutPipelineTransportAction.java @@ -9,6 +9,7 @@ package org.elasticsearch.action.ingest; import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.admin.cluster.node.info.NodesInfoMetrics; import org.elasticsearch.action.admin.cluster.node.info.NodesInfoRequest; import org.elasticsearch.action.support.ActionFilters; import org.elasticsearch.action.support.master.AcknowledgedResponse; @@ -66,7 +67,7 @@ protected void masterOperation(Task task, PutPipelineRequest request, ClusterSta ingestService.putPipeline(request, listener, (nodeListener) -> { NodesInfoRequest nodesInfoRequest = new NodesInfoRequest(); nodesInfoRequest.clear(); - nodesInfoRequest.addMetric(NodesInfoRequest.Metric.INGEST.metricName()); + nodesInfoRequest.addMetric(NodesInfoMetrics.Metric.INGEST.metricName()); client.admin().cluster().nodesInfo(nodesInfoRequest, nodeListener); }); } diff --git a/server/src/main/java/org/elasticsearch/rest/action/admin/cluster/RestNodesInfoAction.java b/server/src/main/java/org/elasticsearch/rest/action/admin/cluster/RestNodesInfoAction.java index ad591c07ffec0..fc04374411536 100644 --- a/server/src/main/java/org/elasticsearch/rest/action/admin/cluster/RestNodesInfoAction.java +++ b/server/src/main/java/org/elasticsearch/rest/action/admin/cluster/RestNodesInfoAction.java @@ -8,6 +8,7 @@ package org.elasticsearch.rest.action.admin.cluster; +import org.elasticsearch.action.admin.cluster.node.info.NodesInfoMetrics; import org.elasticsearch.action.admin.cluster.node.info.NodesInfoRequest; import org.elasticsearch.client.internal.node.NodeClient; import org.elasticsearch.common.Strings; @@ -28,7 +29,7 @@ @ServerlessScope(Scope.INTERNAL) public class RestNodesInfoAction extends BaseRestHandler { - static final Set ALLOWED_METRICS = NodesInfoRequest.Metric.allMetrics(); + static final Set ALLOWED_METRICS = NodesInfoMetrics.Metric.allMetrics(); private final SettingsFilter settingsFilter; diff --git a/server/src/main/java/org/elasticsearch/rest/action/cat/RestNodeAttrsAction.java b/server/src/main/java/org/elasticsearch/rest/action/cat/RestNodeAttrsAction.java index a9a1a09a4f01c..9a032ce064cf6 100644 --- a/server/src/main/java/org/elasticsearch/rest/action/cat/RestNodeAttrsAction.java +++ b/server/src/main/java/org/elasticsearch/rest/action/cat/RestNodeAttrsAction.java @@ -9,6 +9,7 @@ package org.elasticsearch.rest.action.cat; import org.elasticsearch.action.admin.cluster.node.info.NodeInfo; +import org.elasticsearch.action.admin.cluster.node.info.NodesInfoMetrics; import org.elasticsearch.action.admin.cluster.node.info.NodesInfoRequest; import org.elasticsearch.action.admin.cluster.node.info.NodesInfoResponse; import org.elasticsearch.action.admin.cluster.state.ClusterStateRequest; @@ -60,7 +61,7 @@ public RestChannelConsumer doCatRequest(final RestRequest request, final NodeCli @Override public void processResponse(final ClusterStateResponse clusterStateResponse) { NodesInfoRequest nodesInfoRequest = new NodesInfoRequest(); - nodesInfoRequest.clear().addMetric(NodesInfoRequest.Metric.PROCESS.metricName()); + nodesInfoRequest.clear().addMetric(NodesInfoMetrics.Metric.PROCESS.metricName()); client.admin().cluster().nodesInfo(nodesInfoRequest, new RestResponseListener(channel) { @Override public RestResponse buildResponse(NodesInfoResponse nodesInfoResponse) throws Exception { diff --git a/server/src/main/java/org/elasticsearch/rest/action/cat/RestNodesAction.java b/server/src/main/java/org/elasticsearch/rest/action/cat/RestNodesAction.java index 296daeeedb7d1..1c4d5f7d8fdbd 100644 --- a/server/src/main/java/org/elasticsearch/rest/action/cat/RestNodesAction.java +++ b/server/src/main/java/org/elasticsearch/rest/action/cat/RestNodesAction.java @@ -9,6 +9,7 @@ package org.elasticsearch.rest.action.cat; import org.elasticsearch.action.admin.cluster.node.info.NodeInfo; +import org.elasticsearch.action.admin.cluster.node.info.NodesInfoMetrics; import org.elasticsearch.action.admin.cluster.node.info.NodesInfoRequest; import org.elasticsearch.action.admin.cluster.node.info.NodesInfoResponse; import org.elasticsearch.action.admin.cluster.node.stats.NodeStats; @@ -90,10 +91,10 @@ public void processResponse(final ClusterStateResponse clusterStateResponse) { NodesInfoRequest nodesInfoRequest = new NodesInfoRequest(); nodesInfoRequest.clear() .addMetrics( - NodesInfoRequest.Metric.JVM.metricName(), - NodesInfoRequest.Metric.OS.metricName(), - NodesInfoRequest.Metric.PROCESS.metricName(), - NodesInfoRequest.Metric.HTTP.metricName() + NodesInfoMetrics.Metric.JVM.metricName(), + NodesInfoMetrics.Metric.OS.metricName(), + NodesInfoMetrics.Metric.PROCESS.metricName(), + NodesInfoMetrics.Metric.HTTP.metricName() ); client.admin().cluster().nodesInfo(nodesInfoRequest, new RestActionListener(channel) { @Override diff --git a/server/src/main/java/org/elasticsearch/rest/action/cat/RestPluginsAction.java b/server/src/main/java/org/elasticsearch/rest/action/cat/RestPluginsAction.java index 16ad6b4c289e4..7aba2c8e38a6d 100644 --- a/server/src/main/java/org/elasticsearch/rest/action/cat/RestPluginsAction.java +++ b/server/src/main/java/org/elasticsearch/rest/action/cat/RestPluginsAction.java @@ -9,6 +9,7 @@ package org.elasticsearch.rest.action.cat; import org.elasticsearch.action.admin.cluster.node.info.NodeInfo; +import org.elasticsearch.action.admin.cluster.node.info.NodesInfoMetrics; import org.elasticsearch.action.admin.cluster.node.info.NodesInfoRequest; import org.elasticsearch.action.admin.cluster.node.info.NodesInfoResponse; import org.elasticsearch.action.admin.cluster.node.info.PluginsAndModules; @@ -61,7 +62,7 @@ public RestChannelConsumer doCatRequest(final RestRequest request, final NodeCli @Override public void processResponse(final ClusterStateResponse clusterStateResponse) throws Exception { NodesInfoRequest nodesInfoRequest = new NodesInfoRequest(); - nodesInfoRequest.clear().addMetric(NodesInfoRequest.Metric.PLUGINS.metricName()); + nodesInfoRequest.clear().addMetric(NodesInfoMetrics.Metric.PLUGINS.metricName()); client.admin().cluster().nodesInfo(nodesInfoRequest, new RestResponseListener(channel) { @Override public RestResponse buildResponse(final NodesInfoResponse nodesInfoResponse) throws Exception { diff --git a/server/src/main/java/org/elasticsearch/rest/action/cat/RestThreadPoolAction.java b/server/src/main/java/org/elasticsearch/rest/action/cat/RestThreadPoolAction.java index 727bd361e5def..c94f40b83856e 100644 --- a/server/src/main/java/org/elasticsearch/rest/action/cat/RestThreadPoolAction.java +++ b/server/src/main/java/org/elasticsearch/rest/action/cat/RestThreadPoolAction.java @@ -9,6 +9,7 @@ package org.elasticsearch.rest.action.cat; import org.elasticsearch.action.admin.cluster.node.info.NodeInfo; +import org.elasticsearch.action.admin.cluster.node.info.NodesInfoMetrics; import org.elasticsearch.action.admin.cluster.node.info.NodesInfoRequest; import org.elasticsearch.action.admin.cluster.node.info.NodesInfoResponse; import org.elasticsearch.action.admin.cluster.node.stats.NodeStats; @@ -77,7 +78,7 @@ public RestChannelConsumer doCatRequest(final RestRequest request, final NodeCli public void processResponse(final ClusterStateResponse clusterStateResponse) { NodesInfoRequest nodesInfoRequest = new NodesInfoRequest(); nodesInfoRequest.clear() - .addMetrics(NodesInfoRequest.Metric.PROCESS.metricName(), NodesInfoRequest.Metric.THREAD_POOL.metricName()); + .addMetrics(NodesInfoMetrics.Metric.PROCESS.metricName(), NodesInfoMetrics.Metric.THREAD_POOL.metricName()); client.admin().cluster().nodesInfo(nodesInfoRequest, new RestActionListener(channel) { @Override public void processResponse(final NodesInfoResponse nodesInfoResponse) { diff --git a/server/src/test/java/org/elasticsearch/action/admin/cluster/node/info/NodesInfoRequestTests.java b/server/src/test/java/org/elasticsearch/action/admin/cluster/node/info/NodesInfoRequestTests.java index a572625fb9f9c..16c0ed251aa3d 100644 --- a/server/src/test/java/org/elasticsearch/action/admin/cluster/node/info/NodesInfoRequestTests.java +++ b/server/src/test/java/org/elasticsearch/action/admin/cluster/node/info/NodesInfoRequestTests.java @@ -32,7 +32,7 @@ public class NodesInfoRequestTests extends ESTestCase { */ public void testAddMetricsSet() throws Exception { NodesInfoRequest request = new NodesInfoRequest(randomAlphaOfLength(8)); - randomSubsetOf(NodesInfoRequest.Metric.allMetrics()).forEach(request::addMetric); + randomSubsetOf(NodesInfoMetrics.Metric.allMetrics()).forEach(request::addMetric); NodesInfoRequest deserializedRequest = roundTripRequest(request); assertThat(request.requestedMetrics(), equalTo(deserializedRequest.requestedMetrics())); } @@ -42,7 +42,7 @@ public void testAddMetricsSet() throws Exception { */ public void testAddSingleMetric() throws Exception { NodesInfoRequest request = new NodesInfoRequest(randomAlphaOfLength(8)); - request.addMetric(randomFrom(NodesInfoRequest.Metric.allMetrics())); + request.addMetric(randomFrom(NodesInfoMetrics.Metric.allMetrics())); NodesInfoRequest deserializedRequest = roundTripRequest(request); assertThat(request.requestedMetrics(), equalTo(deserializedRequest.requestedMetrics())); } @@ -53,7 +53,7 @@ public void testAddSingleMetric() throws Exception { public void testRemoveSingleMetric() throws Exception { NodesInfoRequest request = new NodesInfoRequest(randomAlphaOfLength(8)); request.all(); - String metric = randomFrom(NodesInfoRequest.Metric.allMetrics()); + String metric = randomFrom(NodesInfoMetrics.Metric.allMetrics()); request.removeMetric(metric); NodesInfoRequest deserializedRequest = roundTripRequest(request); @@ -63,7 +63,7 @@ public void testRemoveSingleMetric() throws Exception { /** * Test that a newly constructed NodesInfoRequestObject requests all of the - * possible metrics defined in {@link NodesInfoRequest.Metric}. + * possible metrics defined in {@link NodesInfoMetrics.Metric}. */ public void testNodesInfoRequestDefaults() { NodesInfoRequest defaultNodesInfoRequest = new NodesInfoRequest(randomAlphaOfLength(8)); @@ -80,7 +80,7 @@ public void testNodesInfoRequestAll() throws Exception { NodesInfoRequest request = new NodesInfoRequest("node"); request.all(); - assertThat(request.requestedMetrics(), equalTo(NodesInfoRequest.Metric.allMetrics())); + assertThat(request.requestedMetrics(), equalTo(NodesInfoMetrics.Metric.allMetrics())); } /** @@ -101,7 +101,7 @@ public void testUnknownMetricsRejected() { String unknownMetric2 = "unknown_metric2"; Set unknownMetrics = new HashSet<>(); unknownMetrics.add(unknownMetric1); - unknownMetrics.addAll(randomSubsetOf(NodesInfoRequest.Metric.allMetrics())); + unknownMetrics.addAll(randomSubsetOf(NodesInfoMetrics.Metric.allMetrics())); NodesInfoRequest request = new NodesInfoRequest(); diff --git a/server/src/test/java/org/elasticsearch/action/admin/cluster/remote/RemoteClusterNodesActionTests.java b/server/src/test/java/org/elasticsearch/action/admin/cluster/remote/RemoteClusterNodesActionTests.java index 7c6c1ffdcf98b..18c586a4a51ae 100644 --- a/server/src/test/java/org/elasticsearch/action/admin/cluster/remote/RemoteClusterNodesActionTests.java +++ b/server/src/test/java/org/elasticsearch/action/admin/cluster/remote/RemoteClusterNodesActionTests.java @@ -13,6 +13,7 @@ import org.elasticsearch.action.ActionListenerResponseHandler; import org.elasticsearch.action.admin.cluster.node.info.NodeInfo; import org.elasticsearch.action.admin.cluster.node.info.NodesInfoAction; +import org.elasticsearch.action.admin.cluster.node.info.NodesInfoMetrics; import org.elasticsearch.action.admin.cluster.node.info.NodesInfoRequest; import org.elasticsearch.action.admin.cluster.node.info.NodesInfoResponse; import org.elasticsearch.action.support.ActionFilters; @@ -108,7 +109,7 @@ public void testDoExecuteForRemoteServerNodes() { doAnswer(invocation -> { final NodesInfoRequest nodesInfoRequest = invocation.getArgument(2); - assertThat(nodesInfoRequest.requestedMetrics(), containsInAnyOrder(NodesInfoRequest.Metric.REMOTE_CLUSTER_SERVER.metricName())); + assertThat(nodesInfoRequest.requestedMetrics(), containsInAnyOrder(NodesInfoMetrics.Metric.REMOTE_CLUSTER_SERVER.metricName())); final ActionListenerResponseHandler handler = invocation.getArgument(3); handler.handleResponse(nodesInfoResponse); return null; diff --git a/x-pack/plugin/autoscaling/src/main/java/org/elasticsearch/xpack/autoscaling/capacity/nodeinfo/AutoscalingNodeInfoService.java b/x-pack/plugin/autoscaling/src/main/java/org/elasticsearch/xpack/autoscaling/capacity/nodeinfo/AutoscalingNodeInfoService.java index 118cd19d01b79..578689828de6b 100644 --- a/x-pack/plugin/autoscaling/src/main/java/org/elasticsearch/xpack/autoscaling/capacity/nodeinfo/AutoscalingNodeInfoService.java +++ b/x-pack/plugin/autoscaling/src/main/java/org/elasticsearch/xpack/autoscaling/capacity/nodeinfo/AutoscalingNodeInfoService.java @@ -11,6 +11,7 @@ import org.apache.logging.log4j.Logger; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.FailedNodeException; +import org.elasticsearch.action.admin.cluster.node.info.NodesInfoMetrics; import org.elasticsearch.action.admin.cluster.node.info.NodesInfoRequest; import org.elasticsearch.action.admin.cluster.node.stats.NodesStatsRequest; import org.elasticsearch.action.support.nodes.BaseNodeResponse; @@ -138,7 +139,7 @@ private void sendToMissingNodes(Function nodeLookup, Set< .map(BaseNodeResponse::getNode) .map(DiscoveryNode::getId) .toArray(String[]::new) - ).clear().addMetric(NodesInfoRequest.Metric.OS.metricName()).timeout(fetchTimeout), + ).clear().addMetric(NodesInfoMetrics.Metric.OS.metricName()).timeout(fetchTimeout), ActionListener.wrap(nodesInfoResponse -> { final Map builderBuilder = Maps.newHashMapWithExpectedSize( nodesStatsResponse.getNodes().size() diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/enrollment/TransportNodeEnrollmentAction.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/enrollment/TransportNodeEnrollmentAction.java index 65747c94590b5..152c6aeaf14e7 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/enrollment/TransportNodeEnrollmentAction.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/action/enrollment/TransportNodeEnrollmentAction.java @@ -11,6 +11,7 @@ import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.admin.cluster.node.info.NodeInfo; import org.elasticsearch.action.admin.cluster.node.info.NodesInfoAction; +import org.elasticsearch.action.admin.cluster.node.info.NodesInfoMetrics; import org.elasticsearch.action.admin.cluster.node.info.NodesInfoRequest; import org.elasticsearch.action.support.ActionFilters; import org.elasticsearch.action.support.HandledTransportAction; @@ -142,7 +143,7 @@ protected void doExecute(Task task, NodeEnrollmentRequest request, ActionListene } final List nodeList = new ArrayList<>(); - final NodesInfoRequest nodesInfoRequest = new NodesInfoRequest().addMetric(NodesInfoRequest.Metric.TRANSPORT.metricName()); + final NodesInfoRequest nodesInfoRequest = new NodesInfoRequest().addMetric(NodesInfoMetrics.Metric.TRANSPORT.metricName()); executeAsyncWithOrigin(client, SECURITY_ORIGIN, NodesInfoAction.INSTANCE, nodesInfoRequest, ActionListener.wrap(response -> { for (NodeInfo nodeInfo : response.getNodes()) { nodeList.add(nodeInfo.getInfo(TransportInfo.class).getAddress().publishAddress().toString()); diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/enrollment/InternalEnrollmentTokenGenerator.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/enrollment/InternalEnrollmentTokenGenerator.java index 1e874dead9956..d0acc61570853 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/enrollment/InternalEnrollmentTokenGenerator.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/enrollment/InternalEnrollmentTokenGenerator.java @@ -13,6 +13,7 @@ import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.admin.cluster.node.info.NodeInfo; import org.elasticsearch.action.admin.cluster.node.info.NodesInfoAction; +import org.elasticsearch.action.admin.cluster.node.info.NodesInfoMetrics; import org.elasticsearch.action.admin.cluster.node.info.NodesInfoRequest; import org.elasticsearch.action.support.WriteRequest; import org.elasticsearch.client.internal.Client; @@ -65,7 +66,7 @@ public InternalEnrollmentTokenGenerator(Environment environment, SSLService sslS public void maybeCreateNodeEnrollmentToken(Consumer consumer, Iterator backoff) { // the enrollment token can only be used against the node that generated it final NodesInfoRequest nodesInfoRequest = new NodesInfoRequest().nodesIds("_local") - .addMetrics(NodesInfoRequest.Metric.HTTP.metricName(), NodesInfoRequest.Metric.TRANSPORT.metricName()); + .addMetrics(NodesInfoMetrics.Metric.HTTP.metricName(), NodesInfoMetrics.Metric.TRANSPORT.metricName()); client.execute(NodesInfoAction.INSTANCE, nodesInfoRequest, ActionListener.wrap(response -> { assert response.getNodes().size() == 1; @@ -133,7 +134,7 @@ public void maybeCreateNodeEnrollmentToken(Consumer consumer, Iterator consumer, Iterator backoff) { // the enrollment token can only be used against the node that generated it final NodesInfoRequest nodesInfoRequest = new NodesInfoRequest().nodesIds("_local") - .addMetric(NodesInfoRequest.Metric.HTTP.metricName()); + .addMetric(NodesInfoMetrics.Metric.HTTP.metricName()); client.execute(NodesInfoAction.INSTANCE, nodesInfoRequest, ActionListener.wrap(response -> { assert response.getNodes().size() == 1; NodeInfo nodeInfo = response.getNodes().get(0); From 075e7c68bd3f80b20eac28383234462791e972e8 Mon Sep 17 00:00:00 2001 From: Panagiotis Bailis Date: Thu, 28 Sep 2023 13:23:53 +0300 Subject: [PATCH 04/32] Muting failing test MixedClusterClientYamlTestSuiteIT test {p0=search.vectors/90_sparse_vector/Sparse vector in 7.x} (#100009) --- .../rest-api-spec/test/search.vectors/90_sparse_vector.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/search.vectors/90_sparse_vector.yml b/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/search.vectors/90_sparse_vector.yml index 3644e93d12bbd..27aa0e6e9a20b 100644 --- a/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/search.vectors/90_sparse_vector.yml +++ b/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/search.vectors/90_sparse_vector.yml @@ -145,8 +145,8 @@ "Sparse vector in 7.x": - skip: features: allowed_warnings - version: "8.0.0 - " - reason: "sparse_vector field type supported in 7.x" + version: "all" + reason: "AwaitsFix https://github.com/elastic/elasticsearch/issues/100003" - do: allowed_warnings: - "The [sparse_vector] field type is deprecated and will be removed in 8.0." From a0b0e25e091d79537634300e7ac94d3a2be8f6ac Mon Sep 17 00:00:00 2001 From: Armin Braun Date: Thu, 28 Sep 2023 12:55:45 +0200 Subject: [PATCH 05/32] Simplify FieldCaps Transport Messages Code (#100010) This removes the intermediate GroupBy record and a few list allocations to reduce the memory and runtime overhead of large responses when there isn't as much deduplication. This should help visibly with the not-as-much-deduplication case already, but more importantly it sets up a massive improvement in a follow-up that I'd open right after this is merged! We can assume that the `IndexFieldCapabilities` instances (now a record to make this obvious) will be duplicated across entries massively. So we can deduplicate them when writing through a lookup table, massively shrinking the size of these responses and making operations on the coordinating node much faster as well. --- .../FieldCapabilitiesIndexResponse.java | 75 +++++----- .../fieldcaps/IndexFieldCapabilities.java | 138 +++++------------- .../action/fieldcaps/ResponseRewriter.java | 10 +- .../TransportFieldCapabilitiesAction.java | 7 +- .../FieldCapabilitiesNodeResponseTests.java | 4 +- .../FieldCapabilitiesResponseTests.java | 4 +- 6 files changed, 83 insertions(+), 155 deletions(-) diff --git a/server/src/main/java/org/elasticsearch/action/fieldcaps/FieldCapabilitiesIndexResponse.java b/server/src/main/java/org/elasticsearch/action/fieldcaps/FieldCapabilitiesIndexResponse.java index aac322c4a1de7..b1f844ed66215 100644 --- a/server/src/main/java/org/elasticsearch/action/fieldcaps/FieldCapabilitiesIndexResponse.java +++ b/server/src/main/java/org/elasticsearch/action/fieldcaps/FieldCapabilitiesIndexResponse.java @@ -16,12 +16,11 @@ import org.elasticsearch.core.Nullable; import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.function.Predicate; -import java.util.stream.Collectors; -import java.util.stream.Stream; final class FieldCapabilitiesIndexResponse implements Writeable { private static final TransportVersion MAPPING_HASH_VERSION = TransportVersions.V_8_2_0; @@ -48,7 +47,7 @@ final class FieldCapabilitiesIndexResponse implements Writeable { FieldCapabilitiesIndexResponse(StreamInput in) throws IOException { this.indexName = in.readString(); - this.responseMap = in.readMap(IndexFieldCapabilities::new); + this.responseMap = in.readMap(IndexFieldCapabilities::readFrom); this.canMatch = in.readBoolean(); this.originVersion = in.getTransportVersion(); if (in.getTransportVersion().onOrAfter(MAPPING_HASH_VERSION)) { @@ -68,32 +67,25 @@ public void writeTo(StreamOutput out) throws IOException { } } - private record GroupByMappingHash(List indices, String indexMappingHash, Map responseMap) - implements - Writeable { - GroupByMappingHash(StreamInput in) throws IOException { - this(in.readStringCollectionAsList(), in.readString(), in.readMap(IndexFieldCapabilities::new)); - } - - @Override - public void writeTo(StreamOutput out) throws IOException { - out.writeStringCollection(indices); - out.writeString(indexMappingHash); - out.writeMap(responseMap, StreamOutput::writeWriteable); - } - - Stream getResponses() { - return indices.stream().map(index -> new FieldCapabilitiesIndexResponse(index, indexMappingHash, responseMap, true)); - } - } - static List readList(StreamInput input) throws IOException { if (input.getTransportVersion().before(MAPPING_HASH_VERSION)) { return input.readCollectionAsList(FieldCapabilitiesIndexResponse::new); } - final List ungroupedList = input.readCollectionAsList(FieldCapabilitiesIndexResponse::new); - final List groups = input.readCollectionAsList(GroupByMappingHash::new); - return Stream.concat(ungroupedList.stream(), groups.stream().flatMap(GroupByMappingHash::getResponses)).toList(); + final int ungrouped = input.readVInt(); + final ArrayList responses = new ArrayList<>(ungrouped); + for (int i = 0; i < ungrouped; i++) { + responses.add(new FieldCapabilitiesIndexResponse(input)); + } + final int groups = input.readVInt(); + for (int i = 0; i < groups; i++) { + final List indices = input.readStringCollectionAsList(); + final String mappingHash = input.readString(); + final Map ifc = input.readMap(IndexFieldCapabilities::readFrom); + for (String index : indices) { + responses.add(new FieldCapabilitiesIndexResponse(index, mappingHash, ifc, true)); + } + } + return responses; } static void writeList(StreamOutput output, List responses) throws IOException { @@ -101,22 +93,23 @@ static void writeList(StreamOutput output, List output.writeCollection(responses); return; } - final Predicate canGroup = r -> r.canMatch && r.indexMappingHash != null; - final List ungroupedResponses = responses.stream().filter(r -> canGroup.test(r) == false).toList(); - final List groupedResponses = responses.stream() - .filter(canGroup) - .collect(Collectors.groupingBy(r -> r.indexMappingHash)) - .values() - .stream() - .map(rs -> { - final String indexMappingHash = rs.get(0).indexMappingHash; - final Map responseMap = rs.get(0).responseMap; - final List indices = rs.stream().map(r -> r.indexName).toList(); - return new GroupByMappingHash(indices, indexMappingHash, responseMap); - }) - .toList(); + + Map> groupedResponsesMap = new HashMap<>(); + final List ungroupedResponses = new ArrayList<>(); + for (FieldCapabilitiesIndexResponse r : responses) { + if (r.canMatch && r.indexMappingHash != null) { + groupedResponsesMap.computeIfAbsent(r.indexMappingHash, k -> new ArrayList<>()).add(r); + } else { + ungroupedResponses.add(r); + } + } output.writeCollection(ungroupedResponses); - output.writeCollection(groupedResponses); + output.writeCollection(groupedResponsesMap.values(), (o, fieldCapabilitiesIndexResponses) -> { + o.writeCollection(fieldCapabilitiesIndexResponses, (oo, r) -> oo.writeString(r.indexName)); + var first = fieldCapabilitiesIndexResponses.get(0); + o.writeString(first.indexMappingHash); + o.writeMap(first.responseMap, StreamOutput::writeWriteable); + }); } /** diff --git a/server/src/main/java/org/elasticsearch/action/fieldcaps/IndexFieldCapabilities.java b/server/src/main/java/org/elasticsearch/action/fieldcaps/IndexFieldCapabilities.java index 57a9dd049d26c..ef609a06cb8be 100644 --- a/server/src/main/java/org/elasticsearch/action/fieldcaps/IndexFieldCapabilities.java +++ b/server/src/main/java/org/elasticsearch/action/fieldcaps/IndexFieldCapabilities.java @@ -17,65 +17,54 @@ import java.io.IOException; import java.util.Map; -import java.util.Objects; /** * Describes the capabilities of a field in a single index. + * @param name The name of the field. + * @param type The type associated with the field. + * @param isSearchable Whether this field is indexed for search. + * @param isAggregatable Whether this field can be aggregated on. + * @param meta Metadata about the field. */ -public class IndexFieldCapabilities implements Writeable { - private static final StringLiteralDeduplicator typeStringDeduplicator = new StringLiteralDeduplicator(); - - private final String name; - private final String type; - private final boolean isMetadatafield; - private final boolean isSearchable; - private final boolean isAggregatable; - private final boolean isDimension; - private final TimeSeriesParams.MetricType metricType; - private final Map meta; +public record IndexFieldCapabilities( + String name, + String type, + boolean isMetadatafield, + boolean isSearchable, + boolean isAggregatable, + boolean isDimension, + TimeSeriesParams.MetricType metricType, + Map meta +) implements Writeable { - /** - * @param name The name of the field. - * @param type The type associated with the field. - * @param isSearchable Whether this field is indexed for search. - * @param isAggregatable Whether this field can be aggregated on. - * @param meta Metadata about the field. - */ - IndexFieldCapabilities( - String name, - String type, - boolean isMetadatafield, - boolean isSearchable, - boolean isAggregatable, - boolean isDimension, - TimeSeriesParams.MetricType metricType, - Map meta - ) { - this.name = name; - this.type = type; - this.isMetadatafield = isMetadatafield; - this.isSearchable = isSearchable; - this.isAggregatable = isAggregatable; - this.isDimension = isDimension; - this.metricType = metricType; - this.meta = meta; - } + private static final StringLiteralDeduplicator typeStringDeduplicator = new StringLiteralDeduplicator(); - IndexFieldCapabilities(StreamInput in) throws IOException { - this.name = in.readString(); - this.type = typeStringDeduplicator.deduplicate(in.readString()); - this.isMetadatafield = in.readBoolean(); - this.isSearchable = in.readBoolean(); - this.isAggregatable = in.readBoolean(); + public static IndexFieldCapabilities readFrom(StreamInput in) throws IOException { + String name = in.readString(); + String type = typeStringDeduplicator.deduplicate(in.readString()); + boolean isMetadatafield = in.readBoolean(); + boolean isSearchable = in.readBoolean(); + boolean isAggregatable = in.readBoolean(); + boolean isDimension; + TimeSeriesParams.MetricType metricType; if (in.getTransportVersion().onOrAfter(TransportVersions.V_8_0_0)) { - this.isDimension = in.readBoolean(); - this.metricType = in.readOptionalEnum(TimeSeriesParams.MetricType.class); + isDimension = in.readBoolean(); + metricType = in.readOptionalEnum(TimeSeriesParams.MetricType.class); } else { - this.isDimension = false; - this.metricType = null; + isDimension = false; + metricType = null; } - this.meta = in.readMap(StreamInput::readString); + return new IndexFieldCapabilities( + name, + type, + isMetadatafield, + isSearchable, + isAggregatable, + isDimension, + metricType, + in.readMap(StreamInput::readString) + ); } @Override @@ -92,55 +81,4 @@ public void writeTo(StreamOutput out) throws IOException { out.writeMap(meta, StreamOutput::writeString); } - public String getName() { - return name; - } - - public String getType() { - return type; - } - - public boolean isMetadatafield() { - return isMetadatafield; - } - - public boolean isAggregatable() { - return isAggregatable; - } - - public boolean isSearchable() { - return isSearchable; - } - - public boolean isDimension() { - return isDimension; - } - - public TimeSeriesParams.MetricType getMetricType() { - return metricType; - } - - public Map meta() { - return meta; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - IndexFieldCapabilities that = (IndexFieldCapabilities) o; - return isMetadatafield == that.isMetadatafield - && isSearchable == that.isSearchable - && isAggregatable == that.isAggregatable - && isDimension == that.isDimension - && Objects.equals(metricType, that.metricType) - && Objects.equals(name, that.name) - && Objects.equals(type, that.type) - && Objects.equals(meta, that.meta); - } - - @Override - public int hashCode() { - return Objects.hash(name, type, isMetadatafield, isSearchable, isAggregatable, isDimension, metricType, meta); - } } diff --git a/server/src/main/java/org/elasticsearch/action/fieldcaps/ResponseRewriter.java b/server/src/main/java/org/elasticsearch/action/fieldcaps/ResponseRewriter.java index 37313c435319c..d39dd28b32611 100644 --- a/server/src/main/java/org/elasticsearch/action/fieldcaps/ResponseRewriter.java +++ b/server/src/main/java/org/elasticsearch/action/fieldcaps/ResponseRewriter.java @@ -54,11 +54,11 @@ private static Function buildTra Set nestedObjects = null; if (allowedTypes.length > 0) { Set at = Set.of(allowedTypes); - test = test.and(ifc -> at.contains(ifc.getType())); + test = test.and(ifc -> at.contains(ifc.type())); } for (String filter : filters) { if ("-parent".equals(filter)) { - test = test.and(fc -> fc.getType().equals("nested") == false && fc.getType().equals("object") == false); + test = test.and(fc -> fc.type().equals("nested") == false && fc.type().equals("object") == false); } if ("-metadata".equals(filter)) { test = test.and(fc -> fc.isMetadatafield() == false); @@ -71,7 +71,7 @@ private static Function buildTra nestedObjects = findTypes("nested", input); } Set no = nestedObjects; - test = test.and(fc -> isNestedField(fc.getName(), no) == false); + test = test.and(fc -> isNestedField(fc.name(), no) == false); } if ("-multifield".equals(filter)) { // immediate parent is not an object field @@ -79,7 +79,7 @@ private static Function buildTra objects = findTypes("object", input); } Set o = objects; - test = test.and(fc -> isNotMultifield(fc.getName(), o)); + test = test.and(fc -> isNotMultifield(fc.name(), o)); } } Predicate finalTest = test; @@ -94,7 +94,7 @@ private static Function buildTra private static Set findTypes(String type, Map fieldCaps) { return fieldCaps.entrySet() .stream() - .filter(entry -> type.equals(entry.getValue().getType())) + .filter(entry -> type.equals(entry.getValue().type())) .map(Map.Entry::getKey) .collect(Collectors.toSet()); } diff --git a/server/src/main/java/org/elasticsearch/action/fieldcaps/TransportFieldCapabilitiesAction.java b/server/src/main/java/org/elasticsearch/action/fieldcaps/TransportFieldCapabilitiesAction.java index 91c4f10956866..c9a44c14106ee 100644 --- a/server/src/main/java/org/elasticsearch/action/fieldcaps/TransportFieldCapabilitiesAction.java +++ b/server/src/main/java/org/elasticsearch/action/fieldcaps/TransportFieldCapabilitiesAction.java @@ -417,17 +417,14 @@ private static void innerMerge( final String field = entry.getKey(); final IndexFieldCapabilities fieldCap = entry.getValue(); Map typeMap = responseMapBuilder.computeIfAbsent(field, f -> new HashMap<>()); - FieldCapabilities.Builder builder = typeMap.computeIfAbsent( - fieldCap.getType(), - key -> new FieldCapabilities.Builder(field, key) - ); + FieldCapabilities.Builder builder = typeMap.computeIfAbsent(fieldCap.type(), key -> new FieldCapabilities.Builder(field, key)); builder.add( indices, fieldCap.isMetadatafield(), fieldCap.isSearchable(), fieldCap.isAggregatable(), fieldCap.isDimension(), - fieldCap.getMetricType(), + fieldCap.metricType(), fieldCap.meta() ); } diff --git a/server/src/test/java/org/elasticsearch/action/fieldcaps/FieldCapabilitiesNodeResponseTests.java b/server/src/test/java/org/elasticsearch/action/fieldcaps/FieldCapabilitiesNodeResponseTests.java index a95340e2fffd1..0802e498c43a7 100644 --- a/server/src/test/java/org/elasticsearch/action/fieldcaps/FieldCapabilitiesNodeResponseTests.java +++ b/server/src/test/java/org/elasticsearch/action/fieldcaps/FieldCapabilitiesNodeResponseTests.java @@ -161,8 +161,8 @@ public void testSerializeNodeResponseBetweenOldNodes() throws IOException { // Exclude metric types which was introduced in 8.0 assertThat(outCap.keySet(), equalTo(inCap.keySet())); for (String field : outCap.keySet()) { - assertThat(outCap.get(field).getName(), equalTo(inCap.get(field).getName())); - assertThat(outCap.get(field).getType(), equalTo(inCap.get(field).getType())); + assertThat(outCap.get(field).name(), equalTo(inCap.get(field).name())); + assertThat(outCap.get(field).type(), equalTo(inCap.get(field).type())); assertThat(outCap.get(field).isSearchable(), equalTo(inCap.get(field).isSearchable())); assertThat(outCap.get(field).isAggregatable(), equalTo(inCap.get(field).isAggregatable())); assertThat(outCap.get(field).meta(), equalTo(inCap.get(field).meta())); diff --git a/server/src/test/java/org/elasticsearch/action/fieldcaps/FieldCapabilitiesResponseTests.java b/server/src/test/java/org/elasticsearch/action/fieldcaps/FieldCapabilitiesResponseTests.java index cc7c76553ef99..461000fc22b02 100644 --- a/server/src/test/java/org/elasticsearch/action/fieldcaps/FieldCapabilitiesResponseTests.java +++ b/server/src/test/java/org/elasticsearch/action/fieldcaps/FieldCapabilitiesResponseTests.java @@ -235,8 +235,8 @@ public void testSerializeCCSResponseBetweenOldClusters() throws IOException { // Exclude metric types which was introduced in 8.0 assertThat(outCap.keySet(), equalTo(inCap.keySet())); for (String field : outCap.keySet()) { - assertThat(outCap.get(field).getName(), equalTo(inCap.get(field).getName())); - assertThat(outCap.get(field).getType(), equalTo(inCap.get(field).getType())); + assertThat(outCap.get(field).name(), equalTo(inCap.get(field).name())); + assertThat(outCap.get(field).type(), equalTo(inCap.get(field).type())); assertThat(outCap.get(field).isSearchable(), equalTo(inCap.get(field).isSearchable())); assertThat(outCap.get(field).isAggregatable(), equalTo(inCap.get(field).isAggregatable())); assertThat(outCap.get(field).meta(), equalTo(inCap.get(field).meta())); From 84350c65f553f0f4cc9bd852bbe4d272dea111b4 Mon Sep 17 00:00:00 2001 From: Luca Cavanna Date: Thu, 28 Sep 2023 13:34:17 +0200 Subject: [PATCH 06/32] Adjust executor queue size in AggregatorTestCase (#100011) The queue size needs to be unbounded like the search workers thread pool, to prevent rejections. Rather than setting the queue size to a negative number, we can reuse the search workers executor that is already part of the created thread pool instance. --- .../search/aggregations/AggregatorTestCase.java | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/test/framework/src/main/java/org/elasticsearch/search/aggregations/AggregatorTestCase.java b/test/framework/src/main/java/org/elasticsearch/search/aggregations/AggregatorTestCase.java index d19fd4ecf08a3..5a28bd8b0ea6d 100644 --- a/test/framework/src/main/java/org/elasticsearch/search/aggregations/AggregatorTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/search/aggregations/AggregatorTestCase.java @@ -59,8 +59,6 @@ import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.common.util.MockBigArrays; import org.elasticsearch.common.util.MockPageCacheRecycler; -import org.elasticsearch.common.util.concurrent.EsExecutors; -import org.elasticsearch.common.util.concurrent.EsExecutors.TaskTrackingConfig; import org.elasticsearch.core.CheckedConsumer; import org.elasticsearch.core.Releasable; import org.elasticsearch.core.Releasables; @@ -201,16 +199,8 @@ public abstract class AggregatorTestCase extends ESTestCase { @Before public final void initPlugins() { - int numThreads = randomIntBetween(2, 4); threadPool = new TestThreadPool(AggregatorTestCase.class.getName()); - threadPoolExecutor = EsExecutors.newFixed( - "test", - numThreads, - 10, - EsExecutors.daemonThreadFactory("test"), - threadPool.getThreadContext(), - randomFrom(TaskTrackingConfig.DEFAULT, TaskTrackingConfig.DO_NOT_TRACK) - ); + threadPoolExecutor = (ThreadPoolExecutor) threadPool.executor(ThreadPool.Names.SEARCH_WORKER); List plugins = new ArrayList<>(getSearchPlugins()); plugins.add(new AggCardinalityUpperBoundPlugin()); SearchModule searchModule = new SearchModule(Settings.EMPTY, plugins); From d6526f8d4b48de145c893dc39bcb9ccf02a5ae04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yulia=20=C4=8Cech?= <6585477+yuliacech@users.noreply.github.com> Date: Thu, 28 Sep 2023 13:41:53 +0200 Subject: [PATCH 07/32] [Index Management] Update docs screenshots with for the new index details page (#99973) --- .../index-mgmt/management_index_details.png | Bin 339652 -> 422199 bytes docs/reference/indices/index-mgmt.asciidoc | 6 +++--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/reference/images/index-mgmt/management_index_details.png b/docs/reference/images/index-mgmt/management_index_details.png index edf1a6517f6a2dc7ad7746456f22ea313a85ae77..a975b9952ca88762f7db2d91929a9f309aacd22c 100644 GIT binary patch literal 422199 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 =vRYq4Af5`0?4BUcG2H!Hu`@OeS6m@}LECNYEj zE{1V*-;J^cy{eWx=56XnlX2Jsm5@Z!*DJaOFR88{ZO)$&joCs_IHFL4xh?2Pw0gOj zD>O*xncK5GKXYeH9#04BonWEejhN`JyXZl%=}O7m4Jt66TKNE ziqeNMI(l$86DB|<%64G?=U>K4dTr9;ZAh^byy8@OnqP9Fja5VT-&p|3V4#ArCP4c0 z|NlroUzit;)t$hmQWNi1;8w4G*J<Ed(EC7;Jwcg_+$YYO9$5dX=IG}PS2)>l8A0g}0$A*dT(-~0VJ z7X`H_<-bWD3}~h<0U75*K>3WTn9#k%C!(uNGFKCs%BY7KYYUVA>#Z9iGDKGc%XnCQNq^2GmzC298Z!4A74~v9kZkmhV^=A9Q#8 ze`PnXcsjh}N79TaFOCug!Vm~AK5Z?{b38JcMGlT!7r(Q-q2n7b?t(~j=$F!PLsECu zo#Ld)Y^kFhBG;YhYJ+{v%#92v6enJ7XZUC_r|LccGSAMj^DgWoQO=7?Cz)ejw_ajk zYy9JhcHv}mTx%Q;rc<_N3K{VqNbU@SgH}j3VaV&nUApoIp5*?*Ej&E^W6U7RjA1%b zW?E7EPzL<#c?sb6X8@13SQ*d~&Na5%)nNVCd!wF2dY&X+>xP3_+swa$1&L(#AIY;- zki@1VdBrMiblWnC1UG@$V5M&d<$OY*}e;ja9G#v71TBcG4b?WLMQJ}xR7nH z+NihBEm3dM=Bt&q1CNfJX3Ly}gv55ak>9K{A28t@?=(zGRHvsAc#d4}nA#3v%a@xi ziJbARIV3qXEtQxJlQ;>AG*Z5%l8WL=^JoVDdc0v*+30?12MlE$kM&toUY5eb@mD$p zKoRQYOjvc@gj!cv%#x7!4PMxIMQ8eV_vB|g+iTCeCr&08)1=gTntX zU2jryT78`yI?#RcCe_;?42=&bt(jWhJ`eSClgxw}8?6QhaxS!(#w6QpGUNyWN{-|O zr(n$;1zXLDjFhTufdY?XgR`DV(7)u^br%MV4E6#2&hC z3ezasJxilJ6t_GD3o4_7n5xG~j~5AWg9JWEm93Qx@zxMp9o5u0@=I;kB75K0zAwPM zjb2hMq<%G42=QDSI%nxf5`;`@m0$55`-H34uB2aI=3JATXIz&$5!uL3S`b5nh$`QO z5>=v!2Jt?20)J??R@TC&rwBYJTdDVx#zGfk zI9(~h)?p!Y6=o%g(XY3#zTCoce^z8Wq)fto^1&Ov`bi{P=!#ElG!{Nx{-m#I#Q*t2 zbfH`5nlyNi^|cEs*B2=8U{HhGl&F6QK2_qknr%5u+cl1op65ugmmd)ZfzIT*^oC1} zp@vJp@A?U}`WTW?479%3q~EMAT*74%dGyo3$@QU@B_R+Z_LNZdHy^Ev>_(J+FwW7; zjxP?O;TKj4p_%wz0)DK}Zv@sV!AEzk5aQ=wEmD$-!km!X9wJ!3Dr-f~l@)81Ir8|D zMX-%LCq=x+Zj0Cp|87CI*+RbQ*OAw>sXlg7*xa<7t14tUrtZ?Q8~5`!+J6;W!d_4@ z$wEb<^3}F{i5-FmJ0JfUo$p3@;CeOht4ki=vZ=}^U?4|nx-})(e%i=TWIdD2OV023 zRkgL!=IVYRQI8Yj7|#q_SNy>;bB5{AirnI#P}6yz(C40nm|fE^(b1makzrN2ZW()H zgT23{W(+F|$B0DdP!)U&L~@*kHiBs@+qGOWsU$m#6!mP~!-R525*U^%fk8&-sLw3fZ7ld{dRW4v;M}5E zJ_QX3K8zVs9NP|yW0;8N+EZaSuo0AuqmI0vD+rbbht2QPs}y#5T=H3?rQ4im!DIxyimSsx*vjB?rFS0TBNDYR1aX66I8@MoQm zAnDmG1+%QnhC9=64_9Pw2rP+9qUBpiGN1rDtVXi5V`@HIW!7x&A9sW|zE;jteYbcW z({+v!O`a$SmBDX?!|`99CHhX!Hsfuf-^Saexcvd)sr}%1vl@rpwfp@U@L(E>NJhMt z^u|2i(@sVE#5!YcsYbRkuifbRa^+Xb`v5Zk!D1ZLiS@BSbK03s4U9C$TwOfQ-*|1* zO?-P^$tTsEInkSt)CpJ=Jd9JMT+CD`Ni7iTBY)!-M5Yuv2_l z#C}U?)WikfgTf97=bX1{q`%*m*B<pENopj5+tvL~1Mus`<8888M7DwC6HFs!@HgjnZtvEX-bIQGqm21OBQnCo-e2r&ne zaNHy3^$hboUCJu!F0)5y$3#6B;nB|?2kT}&o!X@v)LpF%Zy?GXHe{ZwUk6*wR7+jg zUx&=MUw#GPL-_PyDY;LuvP^wZl7F%=rgRx6m$Z;JcvU4ktnD!8!F3XJ`WM*h)?t$# zxRiJA`SsN~rLM>NV=cmwkyB{Z?%eB7%|0o?)wCaA7Iw`eukk2u;(!`GKH)Kh+= zNBNrNVi@`PJjwfeQH)2H(=a;yyyR-OhM9o3qx(Sqg%q&T(K#eF>KRWO@>}USq zA3at7lEK@DAmt~!seIS)n-asWccCvlD0CX_SC|L4r_K%By)nue8L~EFqM0PcRJw;f%NJ9PdKAUC{bqBraa3ym=GJ9pXt z`;^DyEWRpaDtWOZOujdMro-*$r=ka-Wd~sbFW~>YF+%QowaHB-9s9mWLaZ%_kg#j) zLwQZ~G(3ti)<2~Gie{$+cstXjq6_8fs%)ud_Ln3T4p#>lI&GFHa(4bWfQ>t9)7PpW z50;|$Uc2n4DauSD9O#eE1a`e|VVyAD);zb}a$4r^i9>icTw6{w1X)ejNdrbN_3?c5 zGpF87#UdR6JLS#pozRJjt(2f>CISt-AQBF`^hZnX(LL7!mRnC&^V)1xx*l#`DnUo8 zJ>NoMHu4$eZ@BhH20QE31@GJj&ny|VBt6}drx55h zBV&|W{vILw-DR)#VPp8-+rgEjp|b1+1INqy*6CUmooN?Ew>LzY;yO4WvWd|OG|w~p zeoEP|Foq2?KzL+fE%Oix-p+~}(eq2y2g6Z*2F?qae7;dVvb6J_sk)vgKSoT2Y_czzS6 zJhvM#R0`DnKwU~x;1(K?v=tP62odGb0z-(Q+wAgoqpN4}r0Py4v`D1t%l5x@B@sLr;E>+|iu`O$X-QQ3~`1?tj=jq5OzH`}^> z{(*6G?c73h#P$)G<7)I@lkxTTwuzt6$`XIt63C@7tiw`8Z#A-cm)-o>J+AgK;hpFf zzvD`ANWBribS&Rc7UIPx^wFB3&6x1Ce!28&d0fgn8s0fMeA8A_72#(`lys>X(odxr z#Q&1xqJ7?&_DdAF)ChTd!IyQuuKG97Js`{mLp1U!H3k7o956%|UQ5-WwLyTSX%Oc`6bZb0mExP4H@aLsD+~l*v zZKk$)Ms)|d0V0jWB2w0>Bqe&Kowg71-WVZ2nq7ku1kg{e5Y-AAv?ZLG8y=dauGkKn ze?{)?be~pzf9`!I50+g8zf##wt{ZB+zLL`SwK3|6eIWMKsBcp(JUFDa+Y}2GWYjl7 zhUoiv;1RtR|KNkE%fVsEVWlFn08!oftl+N35-?`3bHX;h6BnSoXLCrk9uCuV|p7 zu?4jA%Mo?tABhFD{EfOxxcPUa4i}!w!W5a&++^6@73nt9M_LIMd%iSOaUlyT4BtF7 z$AUAfmwa{Rz+5`qFx{v~SZfU!veT5RR4=#*EFzZ>GLGrx^T(Agi~hp$X<6fa8Xjc% zz%+g+*m#W({hUjNV|BcJ%vtxVFwT^RnU5=!!-I^k{8Z+H=#u zNu!@n|s-wrSruy6K0vE+>>*Ap8G`+l3wX2uR|_t(cRnNqA4U%nJR0-+;)qWc8b6`g2d=bU=Nm8(L?xUI^3`75ZmsAXd$aGo%-?b~T(%cuX^ zKtS2n7h69GK8zYs3I5)kiu=*N-on;L&7k*?vEJicSshLBl>2Jsi2nY@Qo*E1^>-O9 z&tD>bu&nvCa-?;Gga`$FmP` z+j%q3J}q`eYE7&B<&VD0W)Tpdz%j?A@uX0zGJB7r{j|ie=cA{mXM26Xf~qsWtSatM z{#%|$Bj^purXf+KxEU_sNezvJxIdtV#(gmn~gZGX3!)IgF|WT|E*E;+D%NR=Umub0Ewu zN^@1Gr%@X{KUwNEkB(N033Beu8{266zO_pe7t_>Pu~xiItRVji6&5Fo=wcx;ua#xB z=yO&2>iI@`5HyYz%QRd);QpDmFXHW+*JOs4bsR8v?(vE+71z+>G`mcT0tcG0dUudb z9WRo8dM2U91&6E6t_|NzH#g0Qp zmcdqgI=Tzrj$T8rCg0f*_4qq2>Y7VEAgv4=8-1s~B)NpPxi9XInLBKv^U=T3Vn8*p zK)=0rNWt7uODzEoa~FOAYqwxdzEVwI)ckwhE!!=Dm*I=aE#y(41I+z<|y^N$P% z6IG_>BBQ^fAdQj{a?B*2s9$r}6s_Q6N;Yh_WaAFTsfO(F>mP@@|xiE~rn3Ts~zl;*4b2tG~a$UvH7_ z-u(XBkVy@_;`1I^q>>fFdi1@BUvQLXBKsnCN7_5p!g#B>A8jOp`I;t&Ne8PQi*GcZ zm=2fFdR)1@fA~oDo+DwihxXY|@=Ojh)emRGl*Zf1A(Vp2gNB!k(FUDAy~O+@Ip$jf z@&kV*DHld7-{<}+ddOev#5y>88$1L{w5sC?BG^G_OZf~h!gDI{<&26S?gq1pYv8Uh zy4&W7#QJn<(jM#&yqSRvbOYbzFdOW2zMdcEri)+}0SLKwY%R3P>4@@;qWbC58|Zzl zN|^`8l9@pQJ(Nz>8gEr*yDuZ*4>2B|Rjp$QcG>Xi!(mhte|DM`GJD<%3@#Wa?=6V^ zVLyTsFes^wo(bHxmPv_mqYQ(iBJ2Ul#Y@Y{GNt*kx;>h+`Ihy&XB<8Rr%z*MjKf}? zj(-n}$A=xrBsQ$)}q&&tFk*=m$R5nu2sdB4&GMujC;$iS0_z%FP>gixIQ5au?HlOl`_@V^ zB$m_MAWsgtVcj@H%!1iOWHz=}9;f0fC&S!AICla_#AdUkBu2>j?n!8k z@!{4`LTBU=WvV^c#;h;7zN!l@wB2m1-CBpeu*gN?J3)n~she+f1yY@MF0xbu?%kg6 z*(!6|DnB9vKbxyE+3d*GuG2Z)fs87*rl)p7o9f z=MK)zJ?8a%c8i7C@Xhl#mJ1%w$Z#3BH~)LTp##vTxE~bTutd9s4piUUg_~?p3K(@UDXv;k$k~> zfnrNyzLbWWxKfVt7#&2B8z=@X2WLqvoQc_$E^pm8PhxmwBwRce)3$_pY? zhMFHfq6Z9#oV?m-;pZLOt8-In7&N0_O_E<28t?^i8uk1%!1`pc=qKn7(G7aNd2$6u z3NIZBl=K^Prm={f()A6SBeJzGmswBhRqdQd`>*IEtP-mI)+w=m=38brkDf_Hy#DkW zGOgJf^=A%ztFM><@mb=)ZMUvZY=34JUUHamMC2Om7PgN2HIYFNSFz0y$)s-SY&dY^ zJ~eSR6po>?BD2~kb zibHmfJSkb^FblKsj>pBGAz(NRRMd1zY+EE#C(bxp$1xMd#O7?pxVD4c9#RJB!(|>7 z`ut95{Pfa~nDXcP)`EZ?#Ki%l;!SS60Ic2Ce&u8O{k6tV9Yi2{-amo4&7-1+C<`A& zLA|VWW|Lbgyp8;XZtNrhsmZM zarX)Cb_yFA?~I#oaF0$I?@c&u%P-MgVd$7C7Jk&?sbu4n>Ro`;9_OAEvGFbuQO1@M zCnnS)>7CXe_KUy2sXxNe>-d+`myHrEWWHG7!7zosVxevW@Ue-y<+E@hpH1EOH)`#4 z{Z%Ya383Cmp0ceB_G-B)MYKibr&d@EfAmi~K*!6m6*H#pV!T&|Kf468UpcCr?QE{f z4!?}XRvt^phEE68)A>~%`z%D}{C(RRaB=h&NqIxYzPoqZn0lbwqu$~Cp;ETGirB`T zpU54-w0*+Md{?91_)sH%VHFMLrEu!LU!pBpWGA+Z&U!e`n;FXJ$Vzv;zMQjN-8)$(pe(#`_Il(6Y1#523u7e3WP9D_)VcFTz>Fb`f?wvP7)(Jc0*#kw zoHi38vX00VwVXJCW@Qf^<%4f;J!yaVcJ$zYPR{Qkd8pb$@=@J7*|i0enlJt8Y8BJ< zhe-e54FF^HlGwI-0UlgZsPVWo(z;yoLG;1d5owJ*k|{yJ14bd>Zt%E=9u`HX){Rwr zzL^10sqQb&jaJV?(^Y)MSMwqWz7&+17#4&>Ksz5Mosvo1o3CQDpd1 zAoRtDlBz$P=`0TW!vi)~U@lGf< z3S#Bq?H&Ucngkot%72_KW?HZWKR6ZcrW}rh2 z9+*q-A3@lAv^} zvEp*u3Z7n=FDBn=M}c8byWN`B)1v(qP@`w-XC|`xMt2zIQf{tunlQEL@nx4gA*r9P zbW$2)-ehE_m_BBnz%PdTz66U!lDl^6Lq za7dlAglGe@k$I(ikG%M2dlawBdYNjqe%Qy6u71&^8kwXKn#jPZ0z=e8fR)Lt?~$Vt#|(jqvOc4lL8J)M7Bd9C>7d|Tv03iyH! zuGUi>j}NK@NVjR#)A06kMk}TlxC%k)W_nrVaL`}_{xcLqH%)O+;xzR+qaF{&D}wC; z4n6Ago9;$UkykSuth5k~7&d3eSz?^P287-1&U4Y)MG2b585K0KLA%Md92co&ru-{!(!|;)O4c>PMb+ zmsw0yKtZT~$)<4Alg%{fa~`ensygPnRl-`kHcN&WUtV(Q;Sn-LK-x9V?UCF3IjUn2 zcI9-tyXyn>sofcQ1{$^78uW0omCawMRHY@Cfmq0~aqr3)eoP}{q1O7vtHl&hUaqz> zry&<56Gb`e7GQzN_In!H1ne@Kc+v{HVG@A00ev@YZJZf^f(62&Ld~7~eWl9g=bn3@J(V3cbOY`%wz@dDO^%U7lRksw2f|(d6 z-J?AH-1?Du2l}l<9n<`Wt*1=44_=W#e;2%;Th5=xiOhi0Hs%MgIr%QsH5S5L){p{i zO(RkH0S6zLjASYbo{w|_I8gxcJ7k@7nUP!KBO1APS|6-JP-in27FbbJPDREUM9{-K z;mW+%%8T1R-(7}}0no$!+s1{L7c2ugkvOykks*sy9*#r4es^&e(*YJ4-Mk0o`dKK1 z9g|4oBts%-<#V`U__MI5Vq3Z{$D*9%N{obrBTpn>{cX8gVLiIjvh|w`gAj&6a4PB~ zz6gdX(qr$Pf1cvG%1v8$VA84yJZZ6T6v05I&+El&I2{+%S0s85UwL^Dtz7mcG>S{d zl`Dxp)gTI8=~)n@0iB|-Ta?%UWZPw0rH1~)Uk0J@gCPaNw%7%It}3_n2j$HXyoG)r zXiEoD=}g36z~7_bwgqegYWA$Vn0-y&e%Z2(&(j!KV;Wl>jY{IrtJgCf*v$^nbG4SA z=x7stQse3S@tnLTfxqoV;Y}rrz$rDhjX$u3JYm!pHXpJ2l_`5Y;uPsg)L(q-g=tvp z?LoZTsLa&xQ7GxXy)v*xZ@-IWu}|C_5r2-yd?@^^Ko)DPq%~i4HHzOs%8}cSFiLGS zY&9()RnalC5{6-^B%CD4H4TK*h3Sm(`R?P{E6ethh~DD?f@WK*i2#C3)4edlR^|D8 z?h4{QDFHuh{VBbiat{<+wrh$#J5E{FY^!;$_Ps&b-Jkta;>l13EY(7dZQ=rdwQGE% zc`p}HpiqC-W4UcipR6|Y?Qfpxofr!#)rEkPYBb~NPs63K{A2Z?z6h2(J+W)n!c(Z! z=LJ%q4RTwndQfAuNH?A985cI(!?atf5m`LkAWq3(aS&?UwOOnWPph(B7<3vdkt;L# zl%I%)I(e*6$vLhd>2ar3tM3zQTl%((oTdsxOZ15EhaPSfSQJTJ7QL`kTO*Afs>G@t z+f(8o{d=gCvW?eYcu6{v|LZ4|!4vj2{GE9l#qi6Mf@~J)G)D&osAp0c712`>C zEYQm>*^tDm0osva<3O25jM%@~+2}2;$~o^%aGO_i3uXVCI)7++&F_IxLg);$Z zKi2v_nFAi4XkqA`&ym38@=f*cuUc@W`ggRex<0-@rQX>g)Mak$vrva7dK$-2R!N@AC|J* zwx(~I{*~VlLL%N1&u!A?ZL#*A5vNgS`g5R}NJL6yrp*S{IVDz4oD`iSz(~AYM#3tuYQcAKV{ak-pM=qTMmJW*(f3h4RcKZ zMOva(V{-w5PqE-@W8@p6Zgw+8g!WKy8Sy|WN-{GTjanT7H}c)yI-HF@9s(LJWT<8< z7XEAPx~dY~L>)kJCFzL#o(MPL!J=p>S^j$xm6$Q#tpVYPnq9A#-O1gbsQH%l+2cIh z=f*XG<||1eEfK@Yt(2n*YdED2cF&Yck+}NlC-`1>#dNNUX4>WZLoAT?K9iu5b=AAy z1OG3v_X@)NggKt@_t)IoJ}dX6?#RrnJdtbSesw_rLf>1gewjjw;tUieU#0_MPi?MB zTY0H-Dccw+Q^H_;?W}htR@kMmL+Bdw4^Pa22EB6e&Oun1Fm_l?W&{hW5N zU@zYJ4~`5W_yT4Txnwjqa0NI5a)`%u(}xe|-V-{Zr-M*^7B*bTv%DU9 zovdo-5zz!|++|WK`6<~>gi5dOiCbhUz)I<#VJ}x&?PM0B{ z$V$64!KmRRcYV9Xz*krf85RWq) zmF!PaIy>4DW~kF$0lAROc-nXPGUk9%piDtM%&4)nmw+K5ALrEF`%5Lwz@hd#u7V+~ z)k=p4*gwO?>k+$wt`ij}IAz>PY=(JSm7(84h+hVkmxm2SWS5#Wy}QP>th7*^tPCxjkKIF`PZWHJrcyj|x=qpVL@C zfxmbI!T=5n0ium2c3Y1In)Gj(R*a_-2O zZAA{vwK0QCuX|V2I$l^fJk_ZuJBS|A{FZ2yOVOLzP_pbbW~nhE*XobYJ#Mozefp{R z5#e`3T8B9zB3ZkP4@dpO*+bdmIOA=_79{c$@+ZxXXC zL4P3}#sE7wDv&}%K#!#L&p+rFDm= z<#wm;lCkUoha1XbaU7Q7pg~x!R?T9i2lCNRaMJJF!!_f-sWW}55$*Hrr9&z)SZ3h& zDtc=*FSuu%xH&Y@(RYvZtwmJS?IC~chBvy+#2pnF8Nv4{`OJz&8IAay3Cb7m~6>!n>}l_W9;34^+ga2{SGmmq z0WyZY8QfJ~RI}Y~Ay%Vb1Op-UW}$5tULdEQIYgeb{AzT*q9@!v4cTc*@Fm{uPkvca zduCsx{mq4;W4b;7zWb2uLNv-au_ZKg9v5PNeoXo($w5+xvCWjPq6$|9{StsCuU>4?5Q@@IM(m5gF~4Yl z?#)xxFesY?A+=-}H(2#?-@LOUAG7@CdGp{7NWKX*hIe*vY(8khzbEvy0XH@OOXtZy z^*i3~eNf`K^aqnSYVQ9|8b3?1xmX)&%zmF8qTJ|$R;!q;XW;pBUZ8mCms;tQKMi|n z*UQ1V{*|3m4ffG=D%mf+`GnIkNR@c3PjR+86fW{LyW@C+2Q#H|X@&%^`BELXYDrSw znK#=Sug|^XbvSJzw#iqhUCJc~UOsQs5L80~pIn-%r; zIBVm%8&a{GNwQ9BvNhmr%tKu-3hf95JZ;#`mtW2E)htjwjpIO8D#pJ4gvOFx*_tKt z#A2`BbT37Xi1KpR=c#Nw?UTRFd&r&ZRCNcE0ZQoP59uPRWnxaxdyE=a;oYgsvfcO~ z01S<`Q2Ht;@pFbZ272Nu4Fg7~iNOdbQ4~uXRjmrxkRg%8zqqg}y{%3-kZNczGSZZa7J3}Wh^zrpPfLvWAYAE<^%+KX~g#JAT~L3=09A7g50 zxFPTk1OSZplwr`HDO1xJi+{MKfgj?%27^=kVVf^sgyz^K62WS3LW74DWf-lJ`W<5~ z2zjzGVlCEqZ-HgA8zJMAG*xw>=@LkyrE3d%6AAp`rJpN}dN4>>?Bc)A8^52r1w*l0 zdzYA=$pupv@ZYL<+%EI0w;3vF^!w*jRY9&Gv_wb1o#I z;p&V&W(GiDUWYD>SrBfwu+YDLsf5>A1Vgze{`TG2`o_Bd{8!s}_6wjAzAEVyaFMG7 zHV=O?Xo3LS+v`09WGJrEx^;GqvdsEgSbsnEAcm%BxS_L?(~n;HHNCbb!KWJQLGd7K zGN;phCA0o4Dz|_(BaYVSp&5_p$Y%e59;p|WN3loWvS0_Q1Tg!$R9CJLE03rahDYEf zRu-k~`8aM()7-V@Bkp=(=U8JCADzpfT@n`BK3@--OB4bZJAA12XI6pu+RnOOw=>dX z{@QUdkb;r19QP_4`29D?dktb*2jGb z!;~mB>UqC&jnr13UQC}D=9sASc{Sq(ToQV=xN}t0FU7t;+tKeF45Q=A@?Fr}sdqzW z>nisDm!{(9ul>#4TuM68wX3#Xg&CA=Mg2+)* zBJ=Cs*RLX=k?D9#r(50NQ!;rQih^t?gQ+k**-H3r)I}2Zgl*2L4om}(`*weAb)eF0{B5r(Scpr}%Yv)SQL9_Z9EWzzNA^(Zi5pW(bD6j25mQoU| zXplyMc-NihWC&CAL*ANit8VUeb5&!pT!NX#PZlgM3T(H=3(NEo6fIR>%?dNYvnb$2 z@_%mwNfQ`azSN>0A}KW5A5}f{APY>YA1Txl19cB)j?>DR^b?94v})L~rG}hYar5L< zEz)1`=N$L_r5lWgr*b0>a_tD}FZ0E+IVCel3h;88zuNG4o!E#=!S|7r(^6^p4-1c{ znDNR@@Z8TsaSZ6sy`Hy-J+;Rbgb}~@ZF4VpSvj}D$ZoJI9)mLB{4J2iS*rxx85Phl z)h+^y*tHvzp_h|bR((!-ROQU`t)cW;TQgAcUMc(7v3ET1YWqgJN~?l@^ao8UlthRH z*W!Qi2VgZ&+TB8p(5P)V9}H6Ecc0vl<&9~m8u}xet3V3DE2+RcY9HS?RWv$F_pM}6^mY*>ZSj8Q)Xq4% z9P9H=ChUw(o@<_e#H+>Ct9uai7OrN09QCv^it@%$JBxEuT8vcVqzJ z*xw+Ww>Jpq`L9OgD9A@+X7_zBkJj(a{latHU@E$SIl+UIM7ax^)vj91V!7QBA~q8gS+%*Lav{edBClLT)Vx16x7N6i144p{a6C!H~AX)`~RWrEugC0w)bHJ zr9>J;kVYD$J0t`_y1P@lJEU7cK)M8^yHir>?(Xi+Z*BD4d+z<6`;GB;Y#pfYhW);4 ztr^dJ<}`=XcbDki%Qrh5-`g!);UHRC6qoVvBGRH;-}Q$t8`9v(yx==PJv^Z+t1fJN>h z42KXT4U}{jX}PJ>Qu8kS7S;lmP9WDcf^_` z_{rJcoW5k=o0!(|C+&ErRh|fyT*(XD?QvzeyYaz%>IK#{ds)OaM?h0|m}qMgl~){= zOB0aV9B*KHg&8e#b%NKa^cbdtEi?u*p2I%8%%)3grdTs{D(T##>2`b$+mpnVJkDMc;zPYY{lSH1WBOO(Cd>GP47WX? z4+lc{SFjz9d~q6_sINb)M-k1NrBbZ|C4Wyv>1$6k*Q^h*R86h547Q+w6SLmoAY!pS zgs6*dujTP)`__nHd$YNW?*nPlCEo(_R~hyUo^#{~tpI)jT|Kf_#=)Z7!tmItG0*Jn zQ6EFi-TcFwn`1l?vZvNcAFy73c)~^wLwIP@_xFehwxGZ=u--l5VR|o#pD4 zZ|ZdkQ`tFOpBGOjE2Pgqls0%J5julOyCdZu-@C^8{Q@=U|InX zcSskV5gVc31;6k_30!+z`g4j-kkD;y_3B#Z;K$3kCC8JXa}75vO(qnBPWrV2fr-%F z!W?loAy@n)dq`lwP1+{0PXZ}qk#I~qeadJN`MeN`JP-Oh?B@-P%2$pOZ;ZP_;IWzW z1hG#TgP@S-89(+$Hd|`o9hHpUeSXn_H+FrnhEV-dgT8ojC`*~-XW|uZ`y$_BYyh_> z@BZug&eC}51%keM)%wd^m!undH_i)AlZF`QI27!$YWF*CE>y}VzL7c{zY?6x?Cy6` z5_bd{A}F3!Az7>`B^1`XG%i#l-y)F)eB}rOgx;8%-Od{k+Hq9dt(715ZnpvkAi7482F z%CgTO1G+D$Q2}WA`9f72EU+EFZS{Zu=>ZSk9gyk0Xp91WV2_u@^am)dKi}UX6xj7% zOTNGVJkNfirg$Coz|5CN(pm}?NTIcS@Q!~G13!W50q+Gacg1OhS*zI_WBETm>ECbk z!NZS{h95{w#*11bHNd6epIP%bxWvK?8sivc_$;l*6XviMSljEmhmk;S>HeLBFddc{ z|MB@fY#+_y97!|f{Eu5Nc-=D$6%R-GqiT|m)y#77U#s%pU*N+ySfd~`TPA4#`JZ_; zPy}+0p%wWHp@Mt_FgNM`N%OK6+&3EBukl+EfOU|d`B`uZOK{5m#m@h)A3*TQgN1Ye z5V?@EVA$^~KKS!3P_vD9VKugUL@HmYRrGVza^nyyF~t4?Xz%aYGlgirhmLjx9;O2w z_P#3$EGxb+EM!$3J||gxfA(jge=qmH{^Uc0(C^-|;--nC+dTc}Yd#@7G!Jzohd~OZ zMETQ%=)wCL#lr%p`2e45K@#hqcZ@d~WQTfLa0(<+tUvqbYoM_2*98e5N8z593Ryl+ ze3AzXtlmp+otjhW|9M-$NI~}E(**~AcDa76|8WiT{TkqVD=(b;z+-NVAL!T|EDEyQ z9x~RuoJ5r9|Kgi-i}$40?-aN=TqUueCu<@wb>5rhdU>!U>~uHhoTF09ywV-Z@XdCB z^vZg7n=OvXKGZ}dNWX`i-}hL&@@PXt!l?iAkL9z*JEa*?{ot=(*sR}4tyTj592}TZ zkvHN3A!F<_0v|wtb9T(;bNH{v{vTi9A0zFr62J6bN?qgRllXH)?oZ{vFKdbc8?RgR zWayRYu|eV7`Arra9L?0(-rUe|wv5sAIjmj0YK4i?OJ*A~kXufeHS|Y2EH1y#kxI|p zJ_SuF-$hm^lAry^mdVnaq0??nK_icFFR_CXY~!%oPKnM}?#j{cixULT$*N{DSD{dP zgI+JOm*?Z^2MXzLMMD=}6iek>je|jJedH!z-+Upr&P>U{IPvhnyx(9|m=d&T2!HI@ zUmN-)$D`*jEr37&MUe1kb1#r0*8D4#|H}aWej~RC#A@VprHZiLUY>m19L{Fg8LyT( zNe1m%BA}rl?TuE;Adq8!ClrRuG$@_%0kG72vm6c@7#I0!J7T<-$XhLS6ly*g#WDW4 z^?UH+rpi%-6&F<4L?RfoJzM(v!gi+{#Awy)7#GJGTqk8I!5G#w2--JTGg z_A52$b=h1^aYNdF@O$N&jJzRK#(#L>e?j@Pa>w8JD*t)q=221iSHH$=Dt|`_?ThD0 z_I-v$ag{`+L?8FY3@4-SvwHF8DWJse#csDNfJv+RMXTa_PjvahHF&bOM!irHupK~q zLZIIH1g<}zF_}AK!g_76%m`Gqdg586;)b(B_OnjGrP*N*HqSVJf8c8+pL@R1|NYGP zWq)n86)HZSfBwKtmA|(dqbzKPXbiQeLZNDu>s+Y;Cdn;(@uFt2>*+RLr$$POme0jz z6eSRYQViluWqPo1p2c=JixHZuE|$a@+a7XOIm@ld>z<=fSs-$`tOC=)b3wrSm{S}W zL6{CMH|bQ~|8Z`a{+#XaY$5$p|9s6p`MueAg}?-$#%0mi&N(M~6|A`;vqWu8TMp~h z58C03wZ-W|GQ)-g-|hyMgcz+?sWu9gGvG3W!y|eVIfQ`rWBO7@m<&Mv$xSC2Wpiwd zM55?3x(&7v;aBZ;CP_Bg1X;|dQ$W{3d_yo^GLZTgSm}()pj~u9)>PQ-IEO{u7|D(K zLgZ`*cF+qoWbO0E(5FKaM{64wJbmjxFDbLm@ldjOvF#PytUD&gs6+xM0d=0n-HAfr zE7A(IVTByo0pqKl=7T_Viopx(wT+Ba0#=e)&PEViZ3y@`wK*p zXJ%?WwQSEoSAHO~i6X5M7>*V>+vqL6q*ltAnITnEw$z zJ+)fN_;)X^YmV7U`{eGHwdb|6&C*wui|jIK0`L7A-&Q}q^_B)+nV%I`M8ehPzH9sc z9$rq~ruaxhPWf(ev9#=>>Iu#mP4N~GMWHnRWx@W-pgjWBQE-rry?zHY$@OkaYdlt~ zo$ASN%rm=V5hxc8B$DY{=L;C$V~Y31(RneOPw;&-^#@F|%eOG9*ecXXZQpOM5Rsav zH1ad8m&>wcypbxa1lbW;Gci7C+kx|(Rc16OxkjT#b@-jPME93r@AkwO;sD#O&8}$L zEN<7!9FS%K&@Qb#gd5I=&1?29oJgqO=LpkifzX|xvf87@cGI8xG#!+JzDDXg07dxW zLJjv^BOIk#JJXkp79MnOx-3BtcWTYaX7N}K=<{J|>i|w7#JVLR%x8mA zArA)Me^jZ;nx+n2`znd&8sB@1Cq6fNfg&nba)Iwjw&~R=J*V?16@_Zm=aITmLeO9% zlF{`g>OED}?o4h>xyd`#(1hxBFw+c8!==??ef}Ylx+Bi3%I7;%Qb8Eh%R*?zc;$LO z9BWTst$#aLY>r!EXEDEy)ou+YkBk+oImXJ5+XmUiLFA#;^4H*U|QVj~}7}`$rd`&rX8$7Yp*g?T1Ai#07nM zp?bgQmPn!T0r>Z&?|)eI`R9;@yS=<|31_w_<6FEZ85w^x;E{`{Yi}G&`x}u}BI|9YlXjO~8C%3IoxiHrzriEfq zt6&Wnc}JasM(v>%XhbT3wjCeEIG>-L#kMD)8>%bCm=wOYRR5sT43|Zn^ChQYO6RUc zt?QMY@kmXGAUC%?a!10m7N>i45PAYfmIvF6%P=SZ?eBoz9xF+(~-bYM9ERqqvO^%}0ozdPV@SuQFzFWzg6 z*|?T!p8w(H-}i9kk+a#s?d4ujm+nI@j$H#0t@k`G;}&Hu7xY*iX{S#SUJf3hlIniF z+jqoK$V0PO-Ns7K$8p!ZZSiapy0;Ry2PPgMll#Bk68uXO(|wx7XUP*2w1A8}4*wAaawN0}C(h9mG%fl&^v$j+1kkzVJwV%VJr1AMl<*!!a z0IP=-9UfI{u|O#IT~X?3?0~$M9G7ZU5xvPS4}3caqx8z=`u5xz9XrwD7>y#j`&N?K zTqm22&tr!SzKfI_=ddeRX+8wKs~s?<=k-cMtG=HdTKyMf#ojQ~qLIt|`aY;9GV~1= zwfooe7L%JAhkyv*rb_FqP+1{GwlJ(N4V>QQ`Purb7ab98-BiZ%z1__t)}CEjA5YY3 z?IH!~a|6;sr>m``fK$+&PUUZu++xlT0Uc{BK1)KOIH~NmH`Fypd)k>rIfc`0K{4N5 zeBda^!c=+_*g~wm!al~^?K~^4y+{uLvoWOND@<2z5wICkXw=wcfX;{B*C&fJHTEod z1rjfKLOY{RDkBOgbbqkJkHtn(=N|4RsQ|MQ)FBk|^8FKdG|8UNDoMoaqxrR#X0j-a zn76Oo2?Un5&!>UHK(JPRo#c;Cj0U|XvA#U``{5vg1=1><*z^C9hW$xEAy&sC}CshL|ek4k;y>%7aF>2nn5B+ItW#_eVtn4Tk>1t=So8syqOj1Jj$v z=MyCSu}wdm5|IX%)5hOzwkTY&iqrLy5zHEr_Sh7GAD~jfm7s;1!S=3FWGgKip`uc_ z=uTUoskM`eAiiBZj%P8KULX|IlgHRU-I)^F(Km@5S`m#QZ!B_AE7E+(|HW+gu0V|g zp*U|c_DV5bU#!LId?4MA`cSq|szq35Yrfn^acaB!(R>hQ?+}O{%))!r7S4ezo>6=e zYlGX2la?L_)5&FOmf!`>`PGI@XEc{zFc6+$J6YeQJzRxhfLG{yLX)ohOWHTXtn>}Q zK7qIY{F2#Z84t<5o&*O3FmUA9cW6AF)QuCpMW?RD0!z z)_USU>Tb-|ljf_JD*J}vafkrwAoJk((e?z-aOK_!j)2d?e&oc(aI|b4oTwyRxaP%E_B++>ciK&#`v*4x@|XGh zCN}caDxedhWt~fa!-D!25Uc;$qP)6~U3a8))G4+*nHAwWs=Yvz2^|vuCW+a6(xki* zWI1T^DYY=7%YxKYde-`CRH}{YTv}>%$s_bO;oI4$9pvx2zses?1j-UBi8oqZp!gu- zsp~VrtDJ35=qX+?$7BR8Umb4>hP=EA3d7@e;jKPej+_ADu0*ZRIfr(u|LRP>@5${H zO8!wOgLlSec>238dYd0q$-@`$XkQMfIMpjmj2^VpOI>4uTo^p)=x4nNbZWSWQSB;# z3+$v+ac%OJOKh@?e!_%H-2nNk^+F$yxug^UyS_q|1pzrs%)=o(+|LD)*(hpg(eo44 zL^yp3Bn;@3n;N7sZC;aMxLhcW0dp(R2sj;~)IH{t=cShF>uVptTn@wO@#0&o{A)RF zql!`2?R8`#p}X71h6fQO^qe;9NIB%(_!e#1bKVy^&*Xebd#P{E^llCLM_Q)9@UMYp zL`qnbxrz|!Z^8wn*ludV=3Q0|;aEwM5ZNP`4*mtBFq%JH%Kt*SJp3TIju>p)2yq-- zd^|wH{*9*nXMPGcz%Iz4KpYtJI}ZE-xKZpEU^v>q=_?Myy`=gwoi%ZEQxkA}#FpBF z%ZrHff_$K#txKG2jxrr?B?X|84tfVf>VDy}WHHdRJOon)TJLIYgiQJp*n~u5XqW;y z>`$pc60hWIt3?Sv4Roosml>Q3)N9`X5s>hKkzBcSQ2)YGYM_-*xIJG&#M1XP_qsg! zE}K2b_am|h>THbK2+~)zcHGxQU=tO7nw`Dz82~2dLDCbov;kwZBION#QA_?&)MFTdv+SqO(}y)US-W=LVhd|{avth zh>I0v7bPbr2g^)(!=n0@SU;Y*XVm_8CyXKROD=d7`Ft zs~Iw0e^I^v`z(9FLf{x=`QL|l9KPeEw?y+CL?vbVJ<5|6uC?LADQ>QtV}*stqixL~ zq)7p>^9!RI1E6LO8oM2^$~yw(_JQ*qY)@txXs3?f=-yzsEO>Zb;l0%6UA_sP(vzQs zgrJfkx-n?*`3K(<_`w9WQaO~wKOu#8l%L?T4;%&x;bDYq3a=Yrg5`-ss2E%|+IGns zAH7}YQa}yelJC!gdk1>DD#Fmq+3P97a3q($TJ=q-iO?{=SC;S zn*%M4ZuM%R*HmQ&X4?*$_4%x%6=pLSnk*IG>Lm`AC+Hph&o@R3HSSM?YuyMKJoytJ(pRauRS!p900apW7;>jA#=?9knRMx#A2|9)C) zQ`^gr9fBP<7=O2F{L?!7c(8QT$#M*Tje)BzL6B;;tY50N-%AxwSxhEbBnVE0ykw#89NW_G>e}!bzgldAe|LFdZ9s0d?19lC8cp?9=C#DAedYcU zuYRX&ww@}8MvOKh+Aua)%xAu3%VZ+Mo8?!7Xs>^KAf4Q}% zVxW;iw>o;X4WK>S(!#)OMS80PeD-8>)!7kBg%F1s9Z`WjVb+35x}vq{Qqaz}yRl=p zY)}CbK#MO38>xXH$oVP&FGtGBelN@Mf1MvL$$ZayMiMlINJ9#fT?nX8cwhYeFY|i; z*DC&dWKYewThvEw!dG#DmH!2&15A%b5$~a5g$en}&m^_WM0D{fl+Z@En>c!xc&X?% z_rY&s{N=`@--59kGFk&rWkC9&`w~7-NwK>(+G08!>E!n2>I;j8o6@@t`V6B%Z`eWe z+m_MMz9bHzET0tDv!ofD!)0~z6e_(h$O6Ysy!n0 zlYQY9s^&VHebfd%O8})Mx!or`7z;4Ey>`qAu&YESdY5v3ur$@<)e7b#-ut>Kwy@jZ zf`R{02mveMvG(`Vqk{PV;_CqSY`ve>X1||cqA{W^NCw~@+o!gFfQDFu%`fD#X#$oT z_6n?S$K!N4YPF$MK~MUC8_WQ@XHN}9yQ1@-iH-pqXOehbMMlf#4fF7H4QAK+<|%@% zGMI3ftx$-Ux5^-t#AO{FS8k{U695!WiF}3=`X)fQ61sH-#v%%I*Ha62JE@H3Ye=Zo z*hERWULI$@L%`nrwOnA~Qc^U^ZC2O#6^sIMYaOlX=>@6Hmz`P@>EUfa$#LPF8it&`VgHnN>_Uqm*3;J&N&{sS(-aF-bRenW^ z06eoM>OsY=*oBCX$1xa$QWZFr8#B68HA<>SfpfGQVBMtzl;zlM0o^IR#0UVV#h2Lf zJAlw`0_bWu&6ZOJ(9*v>$tPOR%Dvek=iVbP}h#XxQz++`9`-oVF0$@CN?_wzdF_@WnQNVXTPuZ$FWF8XiN{XtUTa+iBEY zAkH|POIO5B-JVZiJKau(H3VOE&hH{k$v zcX!xDOa9@--VD4;BIe{hmcI+Z!sAc^f!ZEAb0u3DT*m}{_{%8D26Ka_6GW=>uZSw;S7-&xS~c73ls_5h9JA| zZY#B1meZK5JWCe}OB2D%v%=Wj@p~Zddg=EiEFO$}k;&+c(i70k zFPvh>wAng3RpfL!{%p0<`;k$E2TYfG)dx&RwoPmay^szi&=>LStB%MJ5$)QOmkPVyMigb69eTr0IvJ zI$djX`p0)EUR&d(1~G!TBsgorAGJ*-5>xUvJ*rH$cQ4Zy=Iu1HPq2^iqwC^ zwASu~`|%v>5Go zIhE7_EN3)ZSN$-LGUO#!T&6@a!I3V28|C4G#`QZoBEu-p_Lc)8X;fU3=yaZj;4mp; zfG1YUUGo%#xUKWfzb%K6(S@<8YLl9Th*bRkbS3&HX6zth|HLqu?})~&mQ^~r8j{Z zI#+G&2Zu&Qd}Qf9ny1cBE=?Sxb>i1pQz%!Tg4Nb;Kh#~~O2CDKlX59x{5{CVQ)`QJeLm{Q%#byh-vbB=*mzzO9nXMdXE@kkn z6`ZV&21F@1G{Y!sUpN9VL9*%bBYD{EnOcRwhC;PkeWsbHpP|}RU`njr7tZRH^G}p| z`1gXR-pqfYm44rv_1leAMZ*xYb=2!}p7t4m)>iSo0yS{MN*e$SwmZT{zDFpU>>V0D1fMy@odd;Csup@EQ!VQ;*;1ZyL5a= zX`u`-^4!C7RZz{TsCd_YCHkQd3-f~cKWSp|EZFkRavY%TiwzN8!P#a{d}AmhOQq6$ zK%U@%d~_Mxj0x7wr=mr*x24jvs*10HMkXnsJxjnWN%R9T{*C!m{yNGGkyvR(ul>r- z9xa0_a|za7?e%tTivv*YQYAjHG$dwiy`$&maz9WFw1~1u@;TTVEy%1&Gu7{CcEI>q zsuQ#i)Qr)hC(;$mo6KfoTGskBQUdX)1WV+Mrxl@N`g%GheWHQwGt zSYW;Td4x%3C?#}JvO*@a+I>8K>#9slHH^pQG%Rco1_*4N`)bu9=u6jo&y1L`S3Kf?o-I8A$)1L5Y$=JRRTcXt%Rg2=J8Brokw=&R zRf(TGUVB_<#raJ< z&8NK-%N4?x@$46wA5r0ntJ!!-J$+K!1;a^#V^DnAzk<1byysp${ZxqKn=n$y(~%y5 zxz`~ZWVu}%VafFwmLF+{`Z~;N>k89fZKA&ySEFzx;+DMamXePda$WdZJ7{ns4VDUX*Uov@c==$iZ=eP-#;Dxb(ClEibbbXo<6jD-iq1 zD~bHKP~wc55e6c)Kp;Q3BLTFenC$1YUNl?!`G0;0*}yIlfV0c(sMt^Tx5Up=k`Iz> zz@`4dXvA52A3MBiT@EVww)4GI=CxQF@S)1Oppn`X@F{)rk-A1tH%F$@RINRH$~&bG z?sGj|_aLL1qH>$qL7Sy^gcibz>Sxc#3Khb@pi%6mlFJu#O)R#Xj2~-XSW;ysF*q~3 zUYR~6>IH!+;OS}s&Rn|jh_NC}X^057gQ7{Tvf?9&SVAAR_rA7UJ5Ij23I$L_-uYRS zJf+tQezU&lAJGfVBS}QxnNY_+>4`1X%^Zm%aR68g2WC|PpqWcD5X@Gtfntvszwfgs zr8?lXo0YEBaz}-7$Nk*dgbx;7jTn1km)c`5rg4Z~vk?jSYi>Iqt%~I+=hu{>m08^zVIy-)`Q>I>x?2zN*I}$WcujuPTR>g^iE|h`u3{Cz*2SOB$AcK`FK71 z6LV!(G<62fMCsHAAwI+3#GKTVX`x04cK}}(Y21B*072a@6!-e(wdAWqZwnVsq`WXC zYjVkx+7g)#xH-wC#nHJgRdS(~o10sBW6s+*Hz(KmfrsZ6z}YjFz10syq6zNkJoVAp zH8ybLc8YhRbhwh0u^9Bey$-=v@t2arl?d=T+nJ+nThd3S zg4gsz#HMNMhK%kTnM4UKEh-0z$8eT8-el2&!H7WhiW``a6~`JzU1@gX6vu7q^x%Lw z$T$d%obTrHI76{m2lPpr+yRbgbw8-g_O}K^8ciIoVAxuu7!D8)r@BImBy+2IFmf$| zdg3wvo;t?j1(*pui59*0GUcfOkIkgOC!~2F`?A1ySgyH z?Saii$*n0t#Q)mYha47}F)$^O8wc((JLbSv3XDTIYDpU0EyxRqxx8 z7q>U%CKG&A42d5E0WJ6CJ$q$L9NOXc+I&Lr6uPU+2GEDQB5vzBpL5Iw$oFW>;zFBub~?a%DT(=R)u4c500I zb;1k@FE>obWBj)QWI!N4&C;g|#BGBqj|vN*f3uM^6aQYbAt8`s?WOdw+a8xHKr`Mg zrPKmV6sbOS5E$fM6XDM2BHiw9R_7s-7w256uWoz zS7&76+v8{|*I>r5pGu|K0D=}e$GfuiFRF@c1`k~&YlXt_2J+)8GX%2>8^zI<*U~9( zTyHMR&u^wPvYJsG#@}eQImYlNa2&K+?T1odf~G)`-K+f{{DD{uuQNoV2AkQd)9vb< z&q9H!Gzry#P#S)sm?w7aNBI6Fbd z{SZPVfaSNU!`vV#1MfcZzIq))9*0ZNyZnabXe5P`*;fna$^)UdL47%CpG-K^{fmNh z1M$``ZyokOy$m@j$gs3tqJJGED5J8LvL4eoxPfd6ylOjY#}&Ap5iF3NpKZTW9z`>jMS9ivxat9FIEdu84svNg*|uYfy%` z11t~(c817~0XZAz0{q{T8JeL3$8?6T`%jnkK_X+O6+=zC z@^R>9OL!a@Z|z4w*a@8yRsY+^v4SUTJ|O5ALZQrUc;d(}NdQS#&ha5E&_DGn2H(%b zIBQ|w!1PRU=ns_XaaY?Sz~3qu65Pj_P+UNB#w&@<`0u&BN?st z67Hh_Z3yBqA9;^}97wpKq&@tNBZ?y_7H!$TEM1L2WmD@yQCx87`X#gx(-o$g+wE12 zjVbZ=6{X9PRRBYUY8E+mVNG3fmYrfh8=~I(1u#9k!LD0PZ~t=cPU^zbdHu(nx#|oP zFK`<7r<>%B!@ec}-{(lTxZux$13CM(Dbxn}vGckAvA}$R7B1ai>4wWcDp;RQKj&xN zW#&V>Ur==-kB^OG{b`#2alS2r?t$c%-x-{b#WcDyZYN+;`|+l|fLwUK426&5XK@{w z>}`0T2zq#+LQx=02sO@Fy9d}=%K`j0`Sym^_{}P&wbb(#tjM;swAjU=F-syDPA%%| zjV||%cCVPSz3a#9ZZ`ofH$7O-gQ?!qrFpXmO+CAFyL%#sQ#YnxQ}6xky|(n~axt0H z^8WGpgbI0F1lUmquhE73cm(pF8ZH&=u=v+dN84I5G zlHCD5-4~=Pz>@$qB^t?k(A>fSD*Q3pZGnOiyk}>satH_o>{=|BFv0kHg7W6s*1 zLmnYg1gL%dt5)Xdi?-kZ!jg?al0Nr0;89!z|1N#H_<^{r!J4WckQv zLG8U%(0?eY4hVrzC~Xj+vWXN-Bmm^!KBssS|gWiOI-b9Y85S<6GpC7;qynbYdoAy4nic=u6BAV7`Ntw_%9!zpU zR3_$qM2wUKBgu-JB=2QS@DK(6UoX!wsO{;ai1eRav_>k`(yV4a@?>p+Vm}LJYO0+m z<eqMK1Zh$UO- zp#01{7LP7nD4eEntKP3~z+jaCr}4a2Au*gkukSL2!mTvJFNTT5={eDz24KQQFWJ5I z@8Hh`H>FHG5heqR!{dhY1n%pVBWW@nKK=ucMe*e+7Juv7asm?$8BNBKu1y z!|?E8Xw>+C_H78?Iw^P(p29*+z5I;iXfb|lG>*D=Lt(9r&w^(!*wJ# zICwgD{!lLaF7=tw?a|6hxsz;|!uBPDzWt8K4v~t6d3mYxgek7l(AfJ;2qe35`Nl6Lc$`ouSJca$t`yKTSB_Zx0C#_YM zg#YN0eSDmk+`@T=N|+>%VSIK2YDZW=hF2GnQuEi$MZtbV=mMReV?ttWK_>GWh-9V| zcFl(}=T1jYWQqyDTa31T9m+yWdq*c6zB@3q7pBge*XJrIU{>+WnG`QBs%M^ovo5c? z@BOB%TkXmtZZ$iRtk>r>a2;NKq0~fAJwbh48^j}-c$r|L>3E4Ae460VT?POZcJTp` zqssNFV&k^)d=Va$NZM>TAHVm262?zMEjmIHgKJya+Zr6D4>4&z0Q~bbKsY6n%lU%g zQy{ibMT5b9f2k_c8_+o1cWY6sng9h^0B@B?z!0?nNS>fU7j%M3gLr=_r0>MX>a{z_ zYU*4rP=Kxfb>lI9Ax~z0e+WE^H=c-H-KL6Z`Zwd~XViB^AnX=#2Q;@8_iKinJ zo8Ym!Kc{?t-uEJ)}|9N2k|_nWDyax!>*}RvOk_W}a*RIErzPRmOztIKdjkW`p5W zxZQ)W-iNT{+7`yaKKRU>e{t6ZOd&!Ph2!1bfWHL@E{mK_jMI%T@3+Jt@}J#95LZ_I znFxrE17zbXr(?<1Ts0sTr#T!fDx=jf14$vpbz;a_w|Lu>Y>~io)zU_9iE?ikbD)yQ ze>FdP{Fq?YcdZbN^?e#kuP5yjFz3!}I_b@3y*dy2JCMw`hA@hSMpNEC9?nxjJ=q$w zhwUtwAsE9lXD;7^9*{_VP?{$N~+YNi%@&HP%Sd{IB7gv~nr{jWi?M53$+Q>MTP{hTVj zt5eP6z&$x`A|0o!0Ut@A$9ujxr3eDV^V*5~CD_$Mm!+zvlj&Nt5c-28ICu;6hc8199lS|?Z9qRPLT z-aC!0&i>3n>olBa3NOL1#NZ1)s#ue-099)xUPZT`$c0P|_Bc7to!J#hu(DM%S9D=K zty47bhUzaDpY_ploXRc|x3TJ$0L-uc6w zj>}h04&*pz9zUjpg*1UUqTzH^@x~f1v&oB3pFVx)=;F2Xuh43FYIIp2_>8Cc)nVqV zM#Po}5GxKt@Q?#>;G3i>aIGpFEB-V%n4dh9O7*L~@eU`*abk!aNFdJC0QC(@=#fEV z=~z@L#HT#efD%t&^>n#7ynwSDySYB$v`mm%N0-x;wp&U<# zE`MPg$#-Hx-nR1213>t}c_q6iu@6a7i->_f1A0$sOL*{z%2k*h2`lP}NP?qq_>y447G= z;x-=WjPg;Rtc!R6TZi??iEz=QGj~u(iC)Ic)Cvyo)7!uqd$9g3C8A_AUfE|#(f)(zw zbaQ#q;*U&JCxC_)`jO{J8RSSd#X=neh3JXB?(*fl`+uPFoQFx&pfL9VJPU?~%T3DC z7Kb)4Wb!%Td;SRG_jQ|j?967<{vI9=3iB4_uMUDCL0*5B1YXytHUFj~K#mpw62*KW zSwER+=?DGVm7NQa_uk`|AQxHpiI#kERv)ae%CMM*qizRJ77~bf1U~RtLKbAb6>#)% z?LV8-8amL)L4Qu2Zk~8)C|B?)Lqj$nt^;1bGgzDJ z)GDj9=a;?lJ2k!zd~wg?C?=+_M)SiI2XqXLgT->257Z3At5?mZ(=XzX5>BcapLz*p8OTQ4sO(#Bd z($jhj?>q$c;Rt-zr=Q-hrll?WC93ZBtFu>4b9CFA+N%+C63bTIIh{O}@yvcK85BNC z?kLrEmDKwtj^fvBDM408`tb21!t{vQExVKEc&3lO0IFY?i#^PcoQ<%)s;H)gKG~bY z18vlAMrget*7Dy02E;{tAWE|U!lTV!*{olK@@kH&YRta_j3!GcP9MlZR;?^~-@6w@ zs64nt5K0DusgKxvsyKrYz*KKfSEGUh`&?dO(Y^$*N459ofBZ1sE0h8#sG7M-AT~=u zqojR(q+{Tmo*?r%0JmzANz2Syr$nQvReVeI_(>O34SRxaK6^yMj90Ibu>Cvu{x2Jk zkAwMW&2z51%}1qDHWz#HUHSDBy1a=gf7BLL40*6DIx)0bs}fuMrjt>Jt1aX5rd}kt zbJnNNo>HH)3&hska0%-Rer33sF%y0{H~q0A52gcp_@jSKI?o}?6Va1M4x4uyIBeAA zT5mucxsLpqaIq=VabMgg*#G4Uk8dJ}606(!*p=bk1a6DLQJ!HgOd<3+l6sJ0I6=Jw z&rzX2&Hx3^%NX%Jw%`nB%88z#*CAfzL?NYD>_$C23GUA*Pt}>)L|lAT6grm!_1Z0E ze!f5pfCWwLQ1Z_M8mu=NFtWLZS#%q0pZa( zc~H-Vsmyk43V~g74Vlbntk+Bs4`~aeDm|tt$dyh~1Wsq`iU=2NSD@^rmXk z`nzN`qRIG+9#N$$j04Z3wSL!A%vAb@M)AD;GD4g%ENb@AJD1kE{pii5*5H6BPyTRL zbxzTp7azb8V}}~WyQMueaDoS<-qWd68J{J4JWju#&&`;1&c;uDBy$a4CUc6bU!d*< z&ulQXmmUfU{!A1{wt=p^wmE+Kw42+c=Awdb4|Js!L$e$1g#6fgmC}_YlEP6w{fiAE@@UX8S@zuQWBXFXA>Meb{KT!fu zc`%8M7pvI7WxW{ve*;8uhDSaJxQdb-(3o#eQ^y zo^_a~T3teURol1*AL4W%=qVC)Znx8~`HPzn^C8Ds9r1f`l1#xI-;Vr#ZK^-n*>{lh z?&>a_&voV&U3TA~IQJz=<>$=!A~{ylXbkMVk@sG5|~$!*XFoQ z^NVSPh6Nn?43!h*b;XRhxrJ`H4xgs0Uq@?hSMcIZwQ{-XqUCWvj5?XY9cl?T1vDRE zpCxPe*QK*MEi{lzPYe+^ExuiuexwUX({>s^e)@ETKgY6URm9JcZ+KiG1CA)3!{ zImcy944(jU31Y5%{-o-gheiDC`qLU7hf~WLC(rm|E;wXw zv(Z(z)VjH6V<76tC+r;k@RDN76-7kr-M+HbF*i0#3L_HO*zybYs49VEe;SX>MFeng z2rF+Le9f6PnkrRj6pb79>q~yd(}mA5M=B)$*hXl)1n$O>f!Io~g&Exh>G*JDj%?5% z9}+!oJ;zo6HJc^8J8uduR$ z8v6?ohjo6XauK8zPqdZK-Dd$DBMD~3Q}VyL53mS{{}wI6M>JbfBbTdHS!m&Ko80#B zZjP2gJQBbX;2*+uv~-DB;NE8-^pE7ddkL_A*?%N>3OGY&uF&M=i}nG48hRO0N~F2k z9qWcF${Ml;e2pXkra3GwwoBiIygWbXz(Zy-mQA+I1F4OJ*nP|w_&;Jk`y7kwPlw9q ztEe40@~5|FR$X@PbW}NPA>;Bm@7P11R}Y}#V>4ao9>7w~TK@X3yi%yZ zNzHA9ec01!%+K-`X0hkXFSWo_ZMVC^xZ|<0c*P?SainZ~e|4L=nj;ZTh9U2+2wSZ4 zokf=%*t{@c^LPPi zY%%akA>>`C0=}aKleTq|$WJzRqwmEC{euyBv_m*lzo5v`f*;L^Q}vrJY@d|N_;&aN zpYih%;+Sh>!);=a``WyZBnUk0N+v`6qCqU7TJ0ScL)~U7C~TH7OsV_~kJ-7S=-1rc zwHF92N`}ofTiOkkOM;!K zLVclAg1bIB8~5k!`%4Qz14)CEb$`pzrPMWO!QmZxey1F7V+}%Z&IV+wKts)X`;V^| zpC#7^*^}-u!=_e``U$1x;r>m_88eE-+Tp2#!?+%K;?~dPR|Nm!q2&_}J zjss-F2gwzTZhZ+LNvnXyHkGTq!0@&_uy4G+y^qWxUDpb@DGYtZ;pA{V2gnETKu=Gg z%y6LY6X1TBC{AkERNF8xprU3Tt5Dy`*Qo z+Wxt{=QF1k-;A4tdi^H?_{O%At23LelLWS*Z*AAJHzzskAXtPUg=$t7*xoAU7iCRi zHp2O^E(=vXxN*nH5Xaw#1zoh{UHuIA=H)8imGNbK*t?spi>q!<@B9w)O*zMlOP0rhC*Y3o;G5!TBGgP2Ph0tDhbh{X5_+g=n-1}KzE7ay_{+5$#4i*%kmI!KU zro>7n(}6SV{gc=%GZ>rY`Ck25QmkYnY7zK$2ELj-zmtA_zH$;s8Of zl2HgfdJRDdm>`Lw;HX)+S`7e-HhXW)0mbwgkYMx}Cn}Y8J74(!*n7*cD$_M=ctn&? zx;rGKL>g%+kw&_e?vQQ~kWL8&1O#bmVbLhHl#uR}CEeZd-fKCtx3kCD^X=n3zJKp= zF#d2{i|4uHit{?J^Fl%8s8z{V%g3#%bh(vu zr#V>yWvZ$v#YN%)wf1x5+9#iIqN1(Fq`!n2C__Y*7Soj|v(7(!No<9w3cB9^vUc*? z1X`^3BHOFm2k-c$n@*PWr{=VQZ4XhmP6qjz(qZ$6JpAW-=`XI)nC&gqmI3@xeD>U> zr!iz@F=_b*#4hu=)v?xi*9bc{+CqWEW*$ zXmMnokjg@eE8_@GkJ@HjRzG~D>E6wh3X0Hym`=zh?qUvAWY`DjB|>|7Q9F5>950!y zZce;^^Sqb>6fxc(m*U1Tgyicg{Xm^RDrBhTKPny>t)&pLYyG4RT!pvftHScQ`LpxG z87LSJ(B;x~8xc}{{5c|4M2A*N4g%QGiW1@SKjs72#cid-aT;NY8L-LU?t-Qol@bGC zmj?og2@Pz0-oVW-O=^p(j02z`hDPH08s@Jrf9iV%U7*N1C@uEP#3a!&Zmph=r%(&C6QSm!u%frC*_jiz-yoE+dAjGiPO+ffrF54 z!JyXQ$QirkY7}LA=_X-b`<^Mc3a2O5@Rz0Yxgi!c4ASedU#1N6Q`tq^EA1B9<<&1A z9VhNigxx&_K?HMC8qEoz@e%<@VeRAJFVzerpU!)otq>g99V#|Faq&Tsjy^nHx0`z5 z4C2{9H2+e3oKvri7*yyDQ^kmaphuZnRdk=z82opf7DX>!>=y8`$P;1&=n#puJX-Lz z^Thc6jksouKXJWr?i)v=?YjuRMO}Od%^w^J zj78SIO-H6>^#`tZ78v%NG^Ki8h|lRkj*-u`(13FGrn7#{kh-rOPhQpG*duTSB;22% zXJP;^;i^pFM-_VX(!y_cE^dayWIhYj<0TxPNq(mO)(H?*OP|imx-ec(R4+ROMFs8X(lrA<}x*^i@Kf868&0Py{nanrOaUjGeN1xLbD9rbGRT&^BO3e z6(SvWy4huYao-)Ig+v@7qZvVm-!Q%b*TFtbOe-wpT-a6yR5^XZyM|oCvhQ!6+>51- zx=0}rg3UK*KPeBnyq)nIX*@UKy-tyH{QyD==wV$K8nn&bXfh_TRA)CmYWm}|v0w!S z2)u?h0m08agKBNryZXR0+tm9wG0(s_#(Iz4UJ$Tjt1k6t-vC3JO$(?v(uk9S5Z+tx zXYZZ*53!vVfdG}D>&@fcRmSN@hi4`9tu5k`HaW=GuN$ptoxigJf!ar4le?V*0Gjv& z)*hK-WZL_H$|n#&5OM>X`-d}r4>Bb~>*rFND?q6W`?-Uk)cZ;;r$VWaE!Eqezj4j? zh+aw}1waZ?EPIG?Ls$`E-OaxX>jna0xyAZYDNO148huGL<&lgPM{+$mFz8*2Pa}=B z>O&Nyf-0tREnxtjmA-tk<) zQ5j$cX5{mj&GtW?qn`Mdb>x~4-qFLqZx0zPET<#iG|1u=P%AE&h)z=t*^=>g6#hOzR>O0k@i_`S8N9 zbiPaMJ|&TFJ3%Ks+3(SEnoTajcGF*#eT_XjctR96NpW_`z&d>P>19$L)d!42suMqn z;8(*1-w0xvf;RnFZj+A`)7j!Gh>b9xu} zV@8Z=xaojf4|;gwtia#6yT`P5YE_l5evp7MsfmJ>CUk#3mg)#yWeAO<3ft5=oZy?g zE&YIdOq}DSRT=ZU8Nn4*$!Jn_p04ke)?3{NMIe%m6?%IJsg5)fK8IYMwG43AvnSG% znK^Id=-Si6j;e>(smr5unJOP#`?Mwm&mR)Y=&%ovo5ATm$QRe;zXM&@5@zun?)1-7 ziKx&HAN6;_G={~JmA~#;1?X*Gj;vWahZ*y!p9gV@FE5`NWPA(`_;XF2M4*N9TpraK zmYOV@rtiii2(K)NCFxx7>4PFlD=4D)AOUR~Xn(fcJ`2w@&=Yy(J5-pH-(fTUfe8S3 zIXfgIbHCYv`mXfQ9(-NI-qHLNW_|mIFzX77_~P1F1q}SRf_bKOgOF7WR9Ma>&vk9- z$ce@pKv->4fQ5rUd2?8!Jh?;{g2`h&yqByh`TaGotxm}zHw+t?IVrl;+qzZn;enpm zI}qiiB_aQ9_{2bN*yzVD&Bn(_nV;PBTl)vALLJ8n>Au6$6pXg8k2Eq#borX~V8c#j z)qAtdC`v@m?rn&8KTD>H-XdBk9_jpe@hS6I>$2i<#HdEFY@5NS-$d(T`tcMF0C9=- zNS=}B4=f!Re_!J_Mvv03wNNYCQROdgz8`?xOyOUOjg#WDbc2uT$v58pp2S7=%l4tV zXvgJY>>9oYohrQc(-8&gg|Y*S zLo)onD}zjcZ3H#f)g}S0EJE&EV#$fcAR2E4|3tp>HOPJdL1vL)6*(K0=VOxgtSP!@4YbtTD@ zB@OCN$WVHwS|TbJ9nR)96YHjh3Yg>QBTPr_#<9#foTfZlJbS_x<~e5r2#R7Z?R;SbXj_Qp8$5SAxD)`3=0Wml%2W9?A(H39a46m$ z0maAu7dJ@d(?nc|xuIE<84F}t#DyqR`>nsSt_i!ftEt8g>6!425VACHZ`+P%8KupJ z?psBpH?NCc_F|S;EBQgl!pzmpUsm=TKn9duH&96<`8kXMm-SmU5v~9T`EH_)GSTu9 zbE-|k5%kmDD4h;*g9;g|h*jlLhqG(C>_%&c3%oYri!xzj4RWLhra5zf9(VtffoL+C zOB*~f9YWVW&5Sl!bSDE+G}neej%d-x9yES1_hP1H{WfLnuje5D}k2nKL2+2Dz5G=(4` zh)H)S_AbXars|AAYG_cVI9v1h6VeB}sd4KK`wk93k&GbaSy8~~aeVlZ0}+K_fBzeB z2}B_hgudaC(5M29zR$$(O}6cDBUq<1o30r4v>m82M$~E?i`pDut`M;0?4W^QAB%!ABzNWYG741iDNjWPgE&i*-JC!wld~nv2t#LvAxGs;~`S#7B7j~Re zHWwc`Kc_FV4QxyMYgEt6CSix8Byg?B&R?##OK_jK*L9eip!yaDYpPvfgxlfI&FB6o zVr0-ho`1&dvGcv!Wi_#&K^+Fni%injGhOV1jj8ZBdWnM|vO*^lA^0T;eDKpHNxb?G zm&D0@wXa71!7nxEqae)Y#vrS9*zNv?Sn4VFwKVl6oB zbb?u{$a83h-RF8~UWqLzt!B%YXj?a2-_s>A>Uoy4d_2gO*6#cs6~Eb+OUj#rTC*0} z;O#M2V-O($r|0o9wSS2CW8i4=^y{jYp*3!sl(@iM3mA}rfq&A2c~345gMmf{4PbVS z+gbdG4Q~Au8_)p1_}?IyD~zabG~a9aw?~o{GTJ>(U{bwa=eo|Bx4%yL1NvVVT)M3E z_xHyp>8NZ&69Q!VCud;6gL6};hg~=eh22x8( zqpjNdfIa<DRh_TttnxdBxK`Wn z6ay!-Cz~}~%rhz6fdBJy?;bCj~oBQh;>Zlup*5{k|A$OQ68{QVns5I_di%S zvb>w4PosNcmP?0Ci8@*DftUYi*^fio-gi!{@JY)HPxTz{xyowKU?tB;(9;t6l2K_z zz3%}cwvPJAGz9_t?7zm=z%)%m?5Tj1Wp-M%KS3PdmFvjJV)ZV)@~^>I2S6Bx5v(Am zc^$0zV~++WiGOo<*GBt#s~!)IT#3~EU8N)0!F#>0h4^9$X^5GWq^;bfCF1K;L;=P* zyFr8a@>~BG`{Q8<Ftf8wPo)-*;^#qys#lM1mSce$U7%14s;t*H80SXN&nG@%x@t z=P#^}SGIs$SN@N(g~0Er?FOEVZfE>Iq_+I`m%IT_+oU7@p|B&>aAy)b4x0`)70!wJ zd}k6Au$JKQIUrqtfl_9q?6b&eM0fPW5?QtD7m62&K`qyFZ|SWjfmP#Znei<^fqFYk zB}nTZA`350eUbnBc*}PSvb)4<{lbbM@+Qq1LFPuDN+xm8(n-(E5&QIOZUcy7PmSN6 z`xFPbCKcJTks!WWaoHR&my%%@5a4#r)bum7(Tg5S;qyG+C$|p1`Cun7bg#hXP=()4 z&V-?4uLc|OIu<@0$gQ+e zX*~J%b-l6HWdzN!_OnP45dVZqoz_fw-ST@8;i*q@GA{o*P<5o3c-Tt=^%Df!*Shl% z&Wm2@LX|PRZuHSiI4c)g3t1zvo3UR{I-ig6txnA{q9F zCH}@4(~TLrK39;Rd`=W-tV(%2hS-`t=^_j!r|EP5Z1s#ts80*c2gAXm2R zO3UTZ=zS~?{k?wkKUMd@HDNVN74v{qX{)$BeoWmS#pCya{lu{5DLpBV6`h?z0>J2Q zS!--Mt%S5!!0FIs#=%Ssa9dFNgKoU=U)%*CPmxuMkoccQQ|4>*e+G0;A_h12k-YKF z%e}T6gGWaaLLL{Bg^E2Ybq*@X4O5R0aNW=jQR|Y1t@CNWQ(C>}HQQXpegMj#U1`AX zx9$vON$Rs3U(|sVPgNP2?iHwOLcZF8b{+Qb2LsnpDjps83^o zok6w9M8}(4x!1v<uZDGG~7o(0qDk>959{qAqNHy1OCJ}2CvA;e+`EFSr6s7vBCm-&m&4kq-g)kB(_kGzSjIyihzEyP$0=bEa_ec z2$7*62Xxn5a&Jw!-nN+rc)}d2jv}-J>k^2jwz)x&!cJ90jeJyPnT)A-WI?5LLrdl9Fx|yM1gjAY zw`PKVA`cR1M3*D-HgS>W@A=3viOys$qb#{!dY|ELMoffSyQMD;uGD#s^n@z%)K}QJ zOE>W?qVkd-m*jRX`gDeoaO7zlq;r%%LLCUoxL680?VkrS#Npl+26qBx_$#g;E|Hn$ zGe^3|>{nQV)r=Sb{kj<>-wFz{r~@pZOJ%CAyXo~4;HiNkW=j4Ai6kNj@c+rO!D~zZ zJqVz6HmESefXkqz)nl!u*f-t>&~|jlh&n*Nf!eyE;MpVwr4TV_0q>7|ir#bW24RNF z>TqpOhD(+82;igeet~~ zo$8_d3Zpk>hb(T$EFzg%`gbzZ>0jn|b|DC2`e4I&n}^YqB9h76`3agX+>$^Xiu{N> zl@k%+^T88c6@K576CPywG>em#c)+cfUmt9vS3DS6nvA^69Ef1bsfdJzlHlozoDugq zH;%EkqXBAcNV|Fc1V(;>;ao%*Ki!)uU$(~as*N;eK;E8%Sa6Vn!X&H^JF;hU$`Hbi4s|K(1^mkliH~=1%7_aFpY7*xxrHAY zjm6FG-f6>TukzbI0__`*0VQeFYazq99Puy@fy+_CAB!cbg4>GGc&VpVSL#(?2RiXj;CfyZXv=DhA+FQ{HoL&bu z&TFH6=6Kt6%0R@MCU91x1JJfSN{y}&!+GkoLO)&Y8vtPeM3}!XhVx4;fmwqRZa~g@ zOS!M$H52XX5r{NYGQ_T=e`{h9s0Jog?5xhLid@jOi{5oO+znmn6UTj|^9s*~MR381 zfM8x0Adry(UNw-;&(zIz0XRQlrPWY>ttH3^ED$`);5bCJ+a=z?ySX*A+WELVVbKX{ z)*VOS<9YaWBKF$?5Gb)IA%8kmm;3d%TB~{4@7*bkoYb>ia8lK?Zg>eV(?QSsHmRQg zspdv9(JId8+Cwc>~s0|Qo zyAQ`t(~3D_3)l@k^=`6{9&v9Zzs70SoNCt&Mt3+b@+L=7!3uAv>jU=1-BRCMnpFnNb3iFE783&+A9oW=R7;p5Z}?ARDHDhn8*1_rs%Lr}~=C(mbwh>YP!K zqBs&ZW|KhzIZ@vO^cz+Bt%E$~k%HSQ6}Iv`bWv0T4_cel^>a6%X#)$F>-a?iYCX@3 zE6Z!G!Oc7b#YVSxfMCuGq-_J=efS*?>_QgJHBl5gbD&#p zI}<~~r}X{~QXe}Ct>;_8qyirREa@E*)PVMHN3>3<1v(Zf69wF{ms;@B*)xE3{JVB$ z7=5ux@lT0zSg&?hEy(f`wE1gAikymsI7D=6H--o@Dr zW*)6o?4T5SEGPL(4P zioz_Q$VST7z71>A@(^*eiKPuHGubwRQ1sawG8MEEg6rXpb^bC+&zc=`(V;|ZlTkgsTBgf*sUnt~03Reh<}{Iv8noPcbPPH8KE%TB zdW_pRRW=l7p?9-BqvtnGQR$bc#SVH0jGSIXU9U*SRCC)wFBsEqeaLx-b)DViaqvwJ zpLN3$8l6P8O|Hi7_=o&1-SxzeP$z?VEu$WdInhdUyD&^KvZooZvNt^*e^HsZz`|M0 z7=rh}E$vT^!-!+hZ&OsN`^oV1(d@l8fb7y5hq=on-euSjcJ~Lc@!Zb(NYk!IdveXD zj-X)e$QL^X{H7`Q5NRrVK7v&ey1>mV30${8n%>c~MBGuI4*OVBr&Z47Zis%l{IWnM z_IBbTHccZi9PCq`-*tAem6UU@ZU^~aslZwtbgz4wEf*q!@zhiOd*>@uFd6P}XW%{( zPV7f`zYk;dJ{|hpZ|@A7e6gS3+x8p={pdBAC1#*IhE@J|{zAY|VkG<+QD^=EDSL!` zsAT=83hc|kuPB)0VxhS7gIKdr+*8M^*Kdk}_Aof#wN)LrE;QmB$TS^8d%=i=#atAZ zeL$+zM{=z^temGy12V;s)Med}ZXg4lUB}2&t(d~wO@G%u)-E?`he4I%{nYlaspBMEOAm+NnzdU$C|v9bBOQ?RgL{%WTYM_x z8t)?vDpyX(1>Q9~roYk6!$;aQy^6xpTs|tI`$j|Sx_sIaS9{*0kewi6QH_YD>f=)? z{-X(xEM*D5)g6L9h@wx6Cpr|Puk<;HD-iyK+^nk~Ppf)8j(cWQ9krIvTTKebDTN3# zTHmujVd_jl!P8;4-u=Osddl&(4IZrZ}%$o`XEKv26Pupqc}iO!H@$?Akxw{y1>^>-{b-jzF8lAQq0a29TI5b6QlC z9H8UaC7-qw0<+b_Ogj@jM4sn+(R?NnKa+pW`3+E6i&@t+q>hX-fWDcS1(!Bh!!rbU z5OoJZds(^tt-3FOO}Fu;zkh(<(93u~o0XwmwpPv+M%v>h4FfMS{Jq%KSTH6n`UM85 zZOI6C`Y3R;y{cX!t(Fy|4b5z3K<{M$COuH)Su zpjdVnN`veMW`P`f$Pg^NoO=e2BptRQ>L?&;2hHtZD~yjn$aT<@le7h>*us6pu}YPN zo2+8;MFn{9W`(4PCm`x`f-jX2m^&Bw?1}zY7JJy&WS+vFCvVzS*BlqVz9ECy&m0`< zxvIH>UYdmcsi?*=Ml?xD#L+kScUm)_F$&m@b1zjc&9n?pVOYRQ2M3t-JM&q z^-+8Sn&ZKo?SnK-XV9zjmX5s@2RIa5|TI-v-R6q<(e+@oJtk`^!h$fb|@-N;TCI99>nQtOSSWM=4t}I zWq;27sI@VDV>-D+J+h7R&B=xU?TSX{DL&hNWXt|{pMX#oLE?nt+#s6Tdu1H|O$_Iu z60I_%T*Y+h9S{O;)NP6j4Wu=Am6{(?N=8y*%y%S8jd7aB2)I~jo`K0z4q*C>s^U^4by+ z#2kCcKhVlgbl@Hj1i|J$Zoy3DN>^qt9{XN@&vV17554jR0SD-Ob*T57x~}EnKy_!b z*0jK$>%OB=d+VI9zs!E3BcXkRuN~7R#7fkM6Yic3T=Gr@GzxtVdCwoJ-8z($B(=7B9U4MtmxosQJ`;I0Xv#F6MToVABr8iX+ z;7Si)`bhC2t;V01fDkwMi}8)V>QOiR=Yoh13~Ep?|988;D+6dp3(d1dhYtNs@*FB| zzKsmd9vA(04N=>z_aHttYL6hvAr(wm1pQ_a0M>o&*&cHpZE6b8P4(=ZZdw}JC?Um$ zB4`O&h|tLT9bS}Dy2w>L21REMoiguxNaM(}AQ-YUyMO;yUxBGy()ro36X>8Nobf&_ zsx$CXeJAt8`+P_(I-v0&>E-Rd({My2-mV}{I`M~0J*;u&|&GCydvgRF#V=X-Ym)(gxw!OgSIQNSok zskHW!Oze8yS7FY6_QCRH73ua|TN@w;sK|RnhE`41y5Kc<9{S^VS+A-x&mC8azTajU z9I47e7|3>b9#HF`SG*2-WbfL|Kec>u!sA%)e#D(2jBe6pQfm(?jGrQtX(2n=<DTW^K3RI20@R80oO1v zko64MiL`ToB=g{MEZFh#+wKTn>2~phDe|R9EV_9S4qC6hPJ0dlWepnzw2ut5zjrU4 zb`Cd=r{v%0n1>RsBpvB`8n~rxvOuWaFDwP>okiklLN)r1=O>5I0Y;_mRr{uzpcQ)AcrhgU zme|`aD}(nNJYDn;=SozvpknoD5`;9L6_bTXe1+YndCZqDR|-9f%d**uL9JFP(I|~c zHCr-tj((B^n(d(X0k4Opb)EK(W^t;l^mc$Hgx~~M9)yhxw8eF^2Su-0x zS2g;c>$IwIzU`nrVNtD$sH6IpMA8M=|0O12O>^k`_!XU1@J`tnu`K|6QK|&O=C~a8 z=C19%KBP~(ZAgBjJC5DwdW5PZ>|}u0MQ|YHMBK+FYXUE$KaTb}%Z^c^?_l0gY_@`6$2KS$Wz4Bgd zp)X*|`rbXnOVW@(H=(JBV^oR)?ajACb>(zdJ}(P_DF~o&O$sM!CZcErZ9@8YKp>a?$j? z*Q;bUcLMz2;Hy{&TrMUSoWU%hJaUzFv<9Q|a1$f;NM|pB!4bD!KZiV-`S}KMUnW{o zHZJ+MYU0_%oof|XjVYVB&AOz@Xm)SAAlCajA%~Zjn_9LdM1J4r>|Ztcq{s~xYudLl zazaql@&aG9$;&=f{K%gs%YElAthb_!rbwA z8C=btg(+FJ*CxmgOX(&OR}iBd<;I%MkHJyNI)hq_D(^St^I(0E0MCx=X(I>P93~rm zCich20ii4~`PmuQeD`zV1mOaUPe&B9`(5roYL-8r&uqDWf-CO;E4VBFuv!5049TbP zVu1RWvS@}BOJkf&C9rx+6A<=C*|Mf(JlHtyBA$DwfLrFgjeq)vhD|)=V%lr-J}MNR zh&)$9oKlv5ozjv=*_>v++t>Dfo33%v#gV-XAt=;*TSvQ9tX=VfXoM{VGCOrgU(Ywz zQO|@jsZEvNpg`@R_h~nu0W4L>1ARY%sw#7^Sd~mw`9K8pW$m)@gI7e0aMT}a5! zJr1#6tR?chb{(_1e7;lf;M3L9Sbj$%w>Q-uTeC_x$wZKX5^NUK2|js(<^{EVZm<+G z)b7BiMuRrr<9qH0JJ+gGkFJT=`>gd#E%Uk*0jU$xq}xHwuPdlK{GPMB-vA(q{0=+) z#32wcF#3`@zpvz zAkkSE<}+L2v*NSjG!jGYz8z&;Gc*R>@;Q{2uu^JN93j9n5<8-EIG@HFByDUP{Qi__ ztdt0g*_?UUCI;JRQ^W;DH)^WYY(9GE5vwk)8e2TN=~G6D6O)5;y^*hSCKQI1o1|t% zqFCEEHRQr1%altbb-3A+t}xAn*;aal%+2g2qnPjfQBJ{K#j;EzV#B8qfzz8fu55ei z#nPO?qD7@0Ff}EI-vEVx5gPSc=aq9-n2I*SXVX#tk<8tc8Q0l~1xPfQSWAzlY{rJJ z5A>e&S>c@dm}fB5E%PQ=Vgw$d(F6b)H{SRi&l80}mR~c8zh{fPjSLByX@g52Xv^;O zf`YEriZq=cXcnqtnQ*lUM}rEXgvkg=84ZEPlp5{;P&C{3mRCKb5Oh_Gn>%;_Xmtv0 zT%axPBOkou^ZVx?XxZM?rc z(KDoG1K5uNBI1sbO(#LA~VTXscn2yFEjGZOw^^TGh}%a!43e=aJ#MjU_Wp| zyWGsFl89uG@BBkwA-fU-g}?`BFO9?`2bC{B(k>CJ!&tia4u?+tc~xB$A3RrnOa+=> zZWTC8?sDDYbe}ncxlKL_>=6%S+c0Fl6cO>+M*0F|W5OguV`!hy&Wq6WGuwB_U_UpM z7lvYl0`%?{rX;+n>FZ3j{|uFA>#g~T2}k4hr>3il=Nd-uG#V`uWNUz6x;k&!`3ZzG zm!%^`H!$X`c&F{o7xk|F&}OwRmLXmHnpZh$#UNsoOeJ2p#4hMF1L;?w}m;pK( zZ;P)HRbMXM+Yna1xShGSq>?q;R@fBF?<+sAbn}o$6~KA;eA2{Ud%g0KI_eKVqa2G{ z+U&|3Xtn1d$DK*tj)7GvI4DEA~@r zLa1s2aubqX#4&_(1AqVV=X*E{M`(nrL6Q0!kjt(1+%H%SZ+A>+H7|>Yn1-KM18w8F zJ+nDx8$x5G=eeUZ_)ShX(w-L{-QxTZ&PQ+dIp)*;gEhx6QKm=l#0KvU-X!ix{NKjkllr zJO>IL9SdwmDJ)2fAj_qG7)Qrn>X&Y_QL9m;K{1!olo@hTWs9U-s9_8E5q+Ca;zhq5 zRVzV_0aY^7npNimc3U`s)70wuJV~9yC-3;%D$N87OyuxZfn;VRr!3ka7(CG^OZOYb zV96ja7xvs69cm(epxau=gMRz9v{awj6GMnAg{No8jMTxQ2Al5NS37z}@`#MDG#C)m zWAF3Z^_WBbNR$Ry?&j}yk(Y>_P<@+I` zbIMX@Pv8JGprQ!>v^%Z`@Wk;ioAbvmp`sx!cm0nl*{wk$9z!F^0vICC!R#dD zou_&rv#PQke|)s{q@1bsg?*1fLn#PsAWGBttZLXig_;jQr8kyYoq2g70lqHc{$R;K zklGCWoN)tSoF99CM5N%1{``aeC67-&kZ1v4ywNCZU!ayB5ekFeO50&Z z>zPyHpRTm#J9}rMM>N0odO)Ot-gcsq0$ALqZBZm#q}|E9E>ppb->P55j7}1yHeM`{ zxiPqpND5yh|A-6QT3U1wJB$Ww7!x_xmqnF$ z1W@2H_%4_+aq1;8tK*JUEGn04!pJ*8U8! znR*Ux)@No=jKyL}WOtjGYfpFZ9^Jw!bND8%nG#9HOD&h!RdA5jKIyT$^@TA@ubADu zTO7<|&^!F5z`t>C5#$-OOMU4y3&3D7gE1JR`oeA-2s>_PT}P+#ex$5#%mm( z_C3oygNE9=q64$7SsJZ!=lsqFPf&0Ck^*(1N(+Le^-xQ~UgpYw0?mJV8w=gZPxhAl zg0|8e;jQx2S5`r|1jwl;x$QRKSa-)6k^g0L;UR$P&-<(KzwR&5ZLQvfY@LvIL0UQ` z?qL<6xA4W4^V7N(p`bA^bVd4lfa5zc;onVU-8t|Kj%%YFAg@5Ojkj5(ZKDRy6`lzJN~tYe4; z%&80OWM8Ou>#>uJJh9hv$Bi;<^p^T^#}^w6K9pYW&#q5{fx6!3q^4(`K>OO$W~z3K z9~j9*ROhztsxSO0O~?_KO&9Vsv@=Nb1L&Re+2d$DIRULept>v$YUIMf z!K1TlQxNvyi|XO|nR-h+PP4ArsBK1aqV?6mLikAe+VPu9GfEyWHZ~66)4ZB`%hlz` zMZ8<*H-`#|P7XYTqqk_uo17MTWfPfU$yr+LG=oI6Zaq(>J-!OKtk&v9lJd45ZKr!` z?54rZ-}8q*F2ZpiM;{Qi#ET(D^gsQ{_QBotpNRd$|LaBAUHIAp&62Ls3VRO&CQ9m? zc+mFjr>mfmlDMMrte=gP=>A(aa?!V_17x?6%(VsFHX`Ea<)}T%zss7fON{OfFszL? zOsKzMv(|WT*rfQPw)i42m#k6Z4=n%~?GlI%EC#F507-q(Y5?Gj+!npR0g782m|u7o z400zIa{jCi`tkvH7=Ui)&>K3 z>JM7;YK)GF5cQ~tAC6)T?|UIn5Ug>jGz#HY|7-a zFNb{b_z{NyWmRf&BBK>B{!!e7kdL`7jL_vqAyCivzc@dsl>m(@{%ClNwKlO=1Oqqa5!x3kP^Hwf-~ zkgIFKuS(5RpgYLp7Cx(vw|{rM(l-24016Y*kp-o}Gy-69005KXH+Kh7!HWqM{*82; zKjMf#zi%q2i~ma(6*mi%jRJXpWoW>~5|cqtEEeezbv06&K$z&+*42}}^+nGOOQ7dz z)=m22W*2&`6bc~fd@B9cQU=Dclw4fj+lsm zzm|*he_SpNN`C&b5`%{3lBSCnVB84}5Gehe%7m8*LWtDj=R?vqX4ADUH~8!)TL;X0 zV(8@<=#f{boRc-( zToEgxJ66nz_ka2Hbl3(zMS|nz@Ji|b$0Pq&1qGn_;6rWkpF!6jPDtzLMa_NiKe?a( z=vD)j+!L|hU-&JAGb9a_%7Y}NjzeG?|<_vL4Ohr1c9$Q6)NekkGB{S;m_xLSp^ib z#SwkZzgTH%Is`*<_Xlh4uX2+>>p%n>0pI<_5`u4I{n=9v{13+P|6Qd2caZ>|_&enA z7YVEXKQGeodKY-III~+R+(C*8JYcTsLZskIApxY#`iPxaICSh zktJD2>z)xvGBGF1;7HytYenLCj&?sRBi6fzZ*#UT^^G8xV4d4x`Qh}XP)|c{!(!K( zz5R|)&pmcLBOI~ct{`K};88oKwr+nAMI}Y0e)b#EfBd11iob*1joY@4-97#ZddpGh zwm2F16yl>l{gJ_q6g0V8zF02IZf&j_$DbBSSoY(a-l4_tE6%x(Qz2vg^<%&Jvm&E= zgqVfSxrUgn&%$E)De>P%{^cQ1sg+6n#Wy_0oW|0UDl3>ClJ`i5YyJ4Teq7$TH8wq+<74r3QRZ1v ztUtca5{WMAC4brh%KfW<{g@c_4S#XUOzq5SicD?6BX_ze6e){ae_ch?czgzrbD^RI3f-O>Pt6r-W z>8JzDW^P1X>U3{Q?)3e-rtj%|RL!{#M1o5WAFC>0dyFosVv%4l>sM=ujei>;X;YmC z_3z&(Je+gq)oG|)~p=+_IDc6P?)wiPT-FZ{P{({D7ypD zkilP$`j;{r`8UJ!{?0YjqTq8E`z$*M$_T0ScCU6UDE8aXnJg*-rS` zqUhyA`Y7F_(D)7pB< z_k$8%I~c*cd8l4upbyen(}CMROv_L90URIZnvXyl!34AgUQD!s$6R5IS@qYv>o|E5 zAvbYg>|>X2>^YXEUb^0{liIFRe^9napqBZo^BW_X(F^X9@gL2zs5t&WEuyAB}D zCwe{y^?Dd9f7+MdPgMi=1DRqmc;8Nj2U&`VH|5ySvU{v-WE4*yp@2_ptpTwN_ z;0F{W^C3~I6K~yD-9trb1^#-&{bd<5><#)GZpo3B0B&HqWHE}tiLpJE<|~_N{te1k z%ajQQPkb&u+Bey-cg|JnuC961$PWH$t2xmS4;Jw22b+@Mp6|QLa|p%#!shQ?SX32f zTQ!ywNJsIjcTA9gE9IBlO$mt7=;5`qsp#RgPEiev4u--`$TxhGhR1fx?SpZDVVcFR zby)Rue)!XE$=Cr`wS+QijKKTWORta|(u#?}yR)D@&TC{vxy>F=x{V+2vP;Be7>ke9< z&iH+4{%m;>&O*)GAN0oNPIDK3nXxOcrjIRrKD<85!un9?eb1gvT4eyem%)9U@n5}O zHqI!%m%;0UkDNY~xKJ)Mj;J5U=$%*_xIEG1LNXOqiFW$NY>s-X$<)P3^byuO)$J1# zP`1~4SXZn^3g~(2`C^=Y`Bd`$>GgaOC3=+|eq+xVd~}U~wbY%!ZLXZTvs%$|mu;%H z&C>GR&QNRWpwgymT&LEB!n~(5{+A|1&tIWtF~OOt%f|p>uz?Fr_u07TaoBJ;*%MQs zxsThM%xg(|x>i@RR&hA}s7~?CAaTMRPH*J!;l+uj$ML-8_fGbvn3Bd5*%dogB~1Q| zptZVmU$^da&Xv%nbJwz)lme|(B8RDI{h4hSDsT7T?&%s~UTP>T%qrMy<4N)mo66)I ztBM}9sCak%0Tv2iuH4bJt;x1gDN6Qp*P3OIn*Y^xK&Q(%6}>#4C(=IL?J0L%A6ILx zX7HbgPGK!};k&Ol;Tc39s+$^l>fdxO>-pHsON)2J7T9FwUmnF`F^+W)`Y9Br^qHch zy4GzlzVP$A(3g`v*xL3JRgn?ZidMR6(@X0-yo`42WGf< z@nEbm@oQNF+d+L?9$vujR_~{8j&Bg_Kc_wX;;cAI8NY9xFIp8{$#hV^cadGw;WFo5 zk)vQ5&Q_Q$a=DUFXu2KOmS3d-W==CY!cBgaeWBIy-23Dn==r4C z6+OGVUSkR3O(}e8N1>I+-3oiISbf~nm;;knc~>F(?~D9+hKb(S`Kj1NOxW+s!w7#B z!>BR%%LmJ=!0eia*l_gDKlF_2KmxeeD|}hj9lGCSSua)VZwNp4QL$acO8n$Q3t49f zoe+WmYoq#B@~=T{nC8cWxXiS>R+H|RxQJxTdZdW=y$X*{Zc zNSSg#GE#Dx?tS<0YK_ON*s)EY`Nilq)Gf9y7*ip**?|@o1N-Q^b4s>8DU&1b(Q=9P>RKGX9o(RnE?;A|pQ|3P z3pN`cmf$_@bKSy_A2$!S)*1JfV}(cvxXCY$nmQG|k+VGfYP9u0q@q!!G`n)sVyUGWC z?3WNQ{F{E~TRn#Tn(vlB$~)T$-h3@ws4Bi1w%FsBH)8mU4gEptFeR+#^s=F-ID5#Y zSQk?Hp$FK;{i&)p6Ws;lNSn=vLVxuorTN!nz4G4mlvr1gP_KZJ4v9yaQ zw%^tnk%R7+SDwE(>^#XDAEsgrI2Hd?wJry81h-RLQMr+-+C&YAJKH+cXx-z(%pJb= zlc!8mJWj8B$p0FeWC)=nABH|XzFGUCE=m{T!xY}ve^C0~&X#ZZWY1%=w#wEgd_<(- zy`My(i6Cpc(aS^vUPrwvFQ$d%fk61Yu6#20Ls$FZ4}swfJWc1r6P&OqH&Z}CxNxtS za!Xq9+N1~9D?+WPRy*`w?KZE?kuWflN>8`$qdWw?@+N6X-_I^i{a%9p?T1%ld!H! zhep<8DDK$PPBy8T)~Q+r>w9xsEpj%AR$a!?!W&9df(K8U-fNbmr8+ck`}*v5Lt=bK z^lUqG6M#%x{0M_$ynWNLw69*xBe;|CTB`{rpUHMwsd%R#)BEb4bz-Vx`)l9t#dRCZ zC5=1fmn3sJR{idLJ<=sDx|MO@^;%0-OR8J90R!+eW4bRhkF_0}KoGE4)s5i+)H zl6-3*FljiXsEp$+(yeRp)t~Xb038Tl7}HtfqeTs<1XikqVp9hg#%luVkr$&Li*HiGYPO#5X8WG6tJ)T_>QN2X94dLIXQU-oRC zd#yEkaot20yTQoNah+{GfTEhk9tLvZp&L0tA}s)Ye! zciWZN%r>%5r0#>Q{gZwXL9vS3s0nhm_40dm>5=s76-Vz(M{6H0;{FI(|76_pQ^dAn zn35K6=-w*snf~zZu%BNLyQ22MI^BzUq0#?K`;=F;lgniB&3%luUD3%cUVE}Wgg!W$zi*)V8$^yKPuOMMb6A0O<-!5lB!(q$|B6A|N${-Vqzpk={}1 zy%Rbjz4sbIfB*pkK}rZD5Z;MLysnjGtu@CS^B(soHKeFo-09hv|sjzV;)!Uj7|!j;XVrk>14s3y2f65vnGy863CZyh;49&AR%Ijrv)ALoc~x=oxE zvIGVJ$R$yyN<5Ods(ILaGsUWApq+E^L$mUg!5XY3I7r-PvT*ZjlyZ(&34Eh{To zK2@ePnk9A)_Pw&R%4Zxj+0|M!GKak}qAcUN%k5)L$(ln$d?O~7@p;hFyxF0gZW-O? zYH+6Z; zY;zEVlpYquN=~pAwIssah{x%CD!sJXb3`c%k{hBWI8n zr9`p9Vak<(RpbfR3=Q|#5>^hJcESU-&>o;26JJV^2J>be&yr-#0#$1O=ww)_W^wC` zN!;O)*RZM~PJHjCbopNe-}r@GN5%ZCy4ta9pB%0y)&g-j+a1Jw@g zs+m{BYj(pgL0y?#rtAuVj9y_+^r*N)IBv;U1>30Z16mC#Iw5T}DwxFzZ_YhadrR=svg2jRv>0G0&ubCeF1wY`G)!0<1`aSRj#^4%$pD`#7=p6e42 zVQ6)!75NR91vWxIN-LcN8l!qB5cUWp1MeV7wOG098HE0 z$BFB~fYpE#uXhYAUGJc-SUS|f&HWVnrncH(e8}1`r<)tKZ3VUe=P`B2>*vXlZyLX- zT{MiKc#Q#M1NV?je3E_jJ7T_spl8pv(dyXjP<(JpCSGiGb_i87NeM0CoA}p{h5UJBADOVi3&#AcBV*hk^Sq7`BMg>(#nI4- zg)#-cS6kM#Ogn@n+wNBTVciu+^R?AGysb6Tp(rH-UVnPFVLc=ohBJv@n|qz1n&n_7 zn!6u+k5+C&JU+)-FbcBuYuuo1*7tNT)ETNo?42OR>)H>7D}lb1+MJWq!y7m@Fr?ZS z%>-GpS{N7JJ-ZZhCRcjO0A`QqF0r0sn}D7e+1eyGGr6k(Bpyw&g2oZQ>C9ChbKgI` z!rfU{{c}GO# z4gWrL6=T9&j@O3j1M3n0I?Wcy`Hq5?M{Lk{@rs=*x`Hc};KR|BF86damDu6%4~Jmx zYEKC`r}3!Hv;&ayxo32rMeKa9^?MMlJEx27a4u}*L_ES&{X*|fG%BQac{Sq7)5 zHAeXoUgr#$p#xdpS>@|idw0y_$S44r`ulRSo@oO9Zr65KfaCRa8LLN74a^_Z5&U#C zEw;TC%{Ln`5V=kvue3*-#4iUeXnzAEFt5hkbzMk_Y@HFD_=OptLA{*b0;Bk*YvYl- z7g*(@uB*GYF1T1eYm1*pC1ujzbflrjqqRl!H!mOw0kaNg={*z|x06C@)Lmc!c&Ckf z8Al1&>Z89da|=|djt{kl3FX3?hXmi@QWUzyh{YBNUH8}o4rbRKc=`66mj`*wWB3`U z22e7DhqLTy+_W4kUYF>Cu88tJ+Es*4`81u17MjS@_q;0T5x2)sWYQT1%BoVu`ZHBI zXFt*JEr~e3LF+k;!<&m&Z~I^XesNi3zAw7o(Qnh^MU|TK?W|z=PRp&pU~guoSYJ48 zGZFz~GsIW&67-*BT+b_XT76}T%&kVJNdFQXzx#u66W3k)>zx~^HO=OOV-OIAlOyw1 zT&S}HQP*ejSs4cPxB2gj^Cj*F6lr8>YY)S*8nwu@doH6z+YY|ddS4J3@}m{VajJX8Bn>NYq5UbDQmS0%-X`NEg! zN&>r1b|LI7a!c3`3i;P~&z0sSyAQ!qP@2fbeC#DR9@m;=qjCj?+NHwwWRjY83IwOM zvOEOl2t|MyW@!^c+3zJjor$207Zf!=T+I3yM`)U9HH8gtig=P|Syv{Uv5bAYGoicZ z1>seX-XC9^sN^3v^+3fVF-t|Wsj*gE=lM;iZ!e(Ig3!>pn%=%T*;j}xOXMa6aOs_c zD^&!wF=Pw(({?byv;AW&3mnV{Gm&9%Oq>FmXQ9f9!i~BR)nSAs^CXLq`SKhQZo@bhG|~Avu(6?bd4?bv%I<*59{iu9g?!PJ~Sn7h~Oap-SZ9TrSxE2mJ1c{AxrL7Nf}Om#w=WnC}v!N=v3 zhp`rbAc}Ktini%_MCHS}YEr?+5?G1ePKngJ3FMIjb=Dv&^3(hN8w!Ep8G!#U)pw+A zPE&}!S-I3FWD`Km7!(33gT|PLk8aqy#Bnz>AzNKfQ+NvYQs~OBA31UT+_~%Uaq*>| z4(h(?w#lg6^P6)l+DPijY|HhW93zcH5e3GBcA%?QzC`-V#MqtXiAR|6d$DOL+QY&I zmV#Znf?N=xgknoq8MWhB)7stnsK$@sb&vp)ZrMv^|BmzjWIw#7FXUI>A!fXD$JeOc z?P4r)#_L5O(`-?t^w?Z_|I>lxmfGFR?WuYOw9OA%iJgM1vb!W?(@t)5m7Og(_86ZZdE^7KYMZrDBP6TiJD1VaPGG4`a*AY z<@qajMAG1t)D}zww(rOx?wKODqc1cAqVZaE@ZFQ+x?6tTP+8H@Qp@$m#Ij) zq$US zPY>S9j?<_~;v6`Rg&>xTDyHLQFJ=b7gac_}Frvl$f^+PUW!3F9!q&Z zbd9@Dft(MfV(y0*A}lI01LM=o94SE~)vGlQSTKi{sBQifWiy zRojo;bq5=U6QmDUN2bEHwR8=O@uijt6toJ_#&Eo%Ndqb4sHcIVp844{FY!-y4`7sh ziQJ8e5N?sU!oHj1rP{-*`H_)t{OPf!IcS+m?{KSZ!ZF#{SW{l<=A>8IXzrX`Vt)m) z)Z}G%`|ShhbuW3&=P!s=GD%vww9O{nG3;QxnLK&tU-6I!n#mP^Bd=;h$>(e<=(!`e z7RR)J&>+okB$k1IiwIezdI5aIWRAU(i&q4Bzd)`(O)MWBU?CdW!@BIfE$g7RF zC>d0vSeN=>O6P!30@pf}^lZ)1J;v=lmm&Gv-_35(3CybxU zsa~tmG#y6*fjPYBvTa`3JwYC6)R4>t*)#v7?wYU?u!B||4v849Nz3RvGkYehSjGoH1GG|nGV;yf3sGi z_ZxM1y1F7B#lhz^`9LLe?e^$*Nu`oSV}Y=AF0i1$<#V`FC^VF!`{rXRdYu6#V? zdYbl`o_c}Xs^;(Z!q{QSlt09Ernk1S`qyFAyQQ@r1CCiR+7s>PoPlLi#*a z^EWkX+{&RFo5+5nG)Bi$pL(Aj^3njA(Ev85yD65qA|*oSBSY((q!066a2N|PqCEnkDtHgD3+u_FhmRf?VoJcfJW`l7DfDo$i>6FF7~U#9W% z^F!P_FonN!dW7;NcWDSEQtk>;0osUdP}fjWT#-X>;oXTCd3jYp@JeWgE?6SMk=prc zqn*sG+)Zd@4U5|%7_!>VjgZq6-I{!F(;V-)ihRFv?Y3^EgM2`FH~>jzohh1HMF1U-Q$2hzRiR7X z%s4um|JuXR*D8*(-7VNos|Zj9F6U z!J&|{O^Hj6$7q}Pvu;F()zQBEI%=B{1MiCd9TFHblCljUlkDZ+BtqQHfFM|)xrxSf z1*QJed+pKVN0!eszgTm-l%@8NeU!-2s3t>c9Z=(p4PvF5Q@Jcxh#1M>%9yw zzo&-a+tPVKEGSEN7En0oPz{Fuft2vox@X4QHm8}fUcxk78kO%ViDJ8g%VlPe9m@^& z!NiQ98eQABO#C40jJafGPvO0tb!AqxJ29SNpX=N2%=Tv%)Qh=4%KacWJkgV)sQ|^C z*`7}d+NEe82P~bFWp&$4xEttawdrk)+#-U!WrAGHR?M3PJ(97ddwr=MNjOz8mKAt> zdx@&svl2TWEE)G+c2TiRj) zHB}cpvIz#2RlnZyQ@%$kT?SLOBAyvjK+>cfPx^yzJ3vsWU>(~T9-|G5H*01I{EGyl zhHid6LbHLU)=PQ{_=DIS5Wn(}BJ3;28&*Acaa6)HJ!+I-7y0!B;Wsrc&@!Kc3G@}RmAu+nGr!%g56Zb=Ffn&H8ONJx1^jH-=BzUTq z_@Jy?C}1IudoLcLA(67cz5o>x>9SEieJXlu>`AS8CQ%U4&J$o61ww)gCsn!tW?Bi# zKa!nalmGrMqI*tbp|H~33Kh`nGOlqqLg1gi{jIpeVYv4Te3Ujq1keOy%nm0m4`I9D z!>4fwIHI^L*;VQq-DUbAEFLxr#3*A8QySkaY?j*#vRmPu;gzbht5;R{71BIWO z4W!?Q*{~Rf){wp9?D|?W0JRWR<6HrNY~g&7B%fi^8E1gDxO5W0%pKuhZ;vP~Ctni$ zqKSQ@B%mv-Y**;r^T3$Ud-l9J zRf{Zs#OmoFkHiV`5|ha`p{PrgKm2wcj1f9aMuIlCRA?%3!M^TeUpm}S6A)A#F=E!Gdv&tv9@j!$`ja>9K@ zOt+6*X|`Cs>lUJqr8|7$^(_GOvM$;8%reE8R?(Z-bLZf5ulmyjYKmCZ91MzY)AdNC z-==!;;vU{juqg-E|AFzVsps7;(od2-my@Q>K!WmJh?yt583ZIZA03q3Ts=LX@MW34 zQOr`sUjz*funH?s&ovFASB;OLu!F{q=BrD(Da z@3^0WOZn8s0oM_{MRx8M1X9=o)%6_p-YD*PPuJ59)^7N5=f2%A<#(tFn^nFDoczt3DFnGv#&75bvg$M6sF=^V8#?Mtm+^qsNq0V{ zmf#-OH+^NS7~)d-6#r4$W*{ue#pVcQ34g*|@p*W3JK^VX)9`fgq)Y$dW}-NA^c2*>Mw2g`zT1|X0H#nxArKT-zS2M9&^SfhhVio{1x zgOqBB=p*YuBu}bX-t9ydu2c}Ic;4T;wXj&-JC-Y4WZWuXFIZ!*LuQkYyPdSceCu>I zf}bkSip@UMkGJNT1JZK1_!}VB&>ocWNS={ianw*^wKi)5DInO~D7g+yeN;d!*_K7xJ>{$#E)%p2x>^pg&yuoppr^?di{kf*OxacOuHs zV{IFxkdiu!WjlGRx29X3xS!QqO}u2Y`}WVH!xpwrFf9)#D_iF5{Ho`>`oOGMbaf`w zba$)=5w33s=iLjyg2GtMF(&~Z%tPQ|^g_s0uSvW$JA9Um^50>@x$)ydlb~-kyy~E)ma2`<_HdnH$n(iw^PCtaQk6BX)bX(X@N~qOdMLD>9bv zpV)~%<)1$9Sn9NTAw$odvsW-(YS-HVbiz~I)tq3Q=VlmOrZE zUX{ft-wMnvBb&T!0!ozBSD26{aZ=#bzrFxUI6uDgR;+VSk<-x8$lffE6zR^Xymw%^ z;Tqc{9#%=n7kLt3?rF_6GDLXi4yX)o6q57~EKBgX5F!_M90}@+XfM-U&T_}kcG=>r z&K#IR+BsR~`pimfiTVX*jG5tdseSd!M8I7b5KC*4;g^|JLxh-EB%ptu`&W-~=uMY- z%p_AOTj{8vSnLe5^~pa;p%AG79qq&eNJa6l8;7N-Q@+0{hFWlCD<%#s2W2+9-*gP5 zkF;ZM4e-k}(Nf2j+wwP7Vzh!{==~4G1HqNz1CWr)S1SIS-T@y83ocfkDyV8$KECT@ zpb?T4E~~Bea8jWh)#n`r9I{Q247uX@*v{+BAWq4__ErTd6SY{##Af z{}irAAN#%;_|y2fA*xi{5&MY%aFZ_f7!t2#CAN=QGC@;7*qOgR`TAn|xDTd{*1r^0d?ZDx?k^mD?Ajm@)*$2@f^|6&<}G}u@G>5Vo)$f zH{3Cx(tNI!@{9upj>RXGP=RcD2eAuXM5Rw-Z&dO`Sp4{BSh%r*VERH5RXu zhVnfwG|T@*3wvcB<1n5sJfz{Q%~9WQYpo#_bZ%#8e+NyhK5 zrb(ElcR25oq&aPiKKJBwq2(a@B669Lid$;b?e*~o2bRF!raOU@I)yu(d*u@CFtgxg z9cVV=11Oh(G_wyzI_&eK-|KdLKS^rjBho;s-6Pjp3$FxoFZybC&)xPZ&^S2sJaJn% z+1JnKS{4`#HFM-?=gHV$c7a&2lroLYs-GKxfBy$H+mAgd(5M6(p7YQL zGwORD9G+mo#bv+^qZKJRES6Wvrl_kl2L9-m)w;A3Wq*HFjv zmAfI^p7f59X~gx#n3+7opVKE;0+|8p1Jk-NEt});yrzO7+pv1~66w$DpWS|5K-(OC9a*0#w;Bypm{rH|?i@C940-$N0wR8sb6y^iL9(wKZq{p+nT^ z(oY_#K%_Otwx#ip4x;v!4yTCOor+~1qMHk5=jnIiF+Yx-DU=E`RY--qe#$S)hxjQf zML2kbxSl(pp!H&o0M=qajQ$S$t$K(7Mg*%*|huCZ>*)S)xO4vksFh zyB{C4)xKIEmL|r9G_d%b^a#~%GfB4I$HKarO0wy;aS9Emv>w&!* zvEn@5YSO?forJ)7Y{ws*DiKsow?~? z@e-_E9pS)6j?ZBZ`s@ok$#A@5NIQe0M+n$3fAhDi&m+W51gLaI8;)CrmT6ulKHIr_ z^niSZ5!Z88Z^c@&?zCb!Zhht<@7Bw5{3RoPUa-a1Su7(P#X=yb;s08q|8LpEHBZl& z=iR}V7nMs16yU{dYTl&%*B1nu}<`Ny6Ec7KQ8 z%Yvq~FaSXlf)kBp=CVckwK?dOuGV3b4_c+?n}z0#H-X6sE*J{paqy zb3=UE>10S$pbkxwO+4NDoihiO`OipSA3lr5N9;)fY>w}r%+4ytqK*fj+Yapi_gt}n= z^N}hoB}sVczd+u<|Mc^TW2cu@fce+QAzf7~Fb>7P5iExOT$)snux zR<0P{T@oTHazOF^p~D|(Td)vS$Z*>nEog`QTYG0N!$+YVj)oXheZ~WOJgZJ$oX*cW zXU@7k`Eeu>Tqq4Wec*4DpT79YGZS36`{8yVTlh`t-|Ogg0kmM!OKUQ?gg1&Ut^Z#V zh+&OWYMSG>@X8U59xD5P@bewieV6~@E7xXH+cXnB^hGOZW@M3{{|irjJLH3TlwJs# z2MQg>pu7j1W?%VEgb@tXG|{~Mjv7O*&yCz`@N;C+BjV%xGgzL{_80E`;whwd2ZpV2Tm$?<-hDL_yy5+bu69pvq=D}22|tOAW4g3tw|Ey=LADgkUG9vF2f4l3^LK{oi;~fY zT=k?=J|FNH96{m*E6!hp^qeV|x08SWqBH376Qxt49^!WgIbnH5AN}M|}<{(-SSV<(0G@vgoC1d}Sp zd!bo^@>4xWi`@@G*3nu*Kh9J`aov9RSAXPwck9}kXeFBgj!Y&2JD z3XMAzGrX7jwuQ>gdl;39j005k@2NfYuTrP*y?Hj4q-#qE??ZGOnY#b2+xP3*NtM(i z1-@I$g;>?|wW<)>f4}M9n=wr7tyrE@olT?=Ws6p^C401JSFr@PK4(*RjIP1+XK-(w z#HSX7A07FYr4}_Okn?DGzN^kYd1#znqFLcmlhio#L83j|~6B zU$2@PyB@CFWq_<*Q<>ZGaS<1}nZ0D|8wqLIE0F`48i||>>$K|kjwZN}G&KueNzQvO z^kTS1D~vmP3UnUs?br3R2rxcQOX%BuUG6&nwWr|KJcA%ba-pBt)+2S*gZNT-dnWX8 zbDIQqKMN1lA5l$udUnCNYt%sEz=Qbj{s&l=>9IE5X{Qy4`|G|2=?q>AeB9`MC!LCj z^6%o*O-2D$_c+Iia+_WI;gnRd?Ez&QOn_j@*h-g^)HMN%D6_~k-)lWyvok39 z3nmc|yoy2HMf*qXtT=cbkKLqiJq?Ty*|Sk^N_p@?nO2~EZ48b@-GmW`9nm2mdnp-+ z<$FFvlqLB$l60gK6U%&W^pv7{*S6{OKX^n57f}{>_kNvy+-$SJAiM}E$8^ol{DRex z~G(P#4-&hv31jtO?c1{mmJheT(jC9ZYHnVwQt zfK)ulDgI|H?A6Bg98cQ}t6DuSoF*02fS&qVdAvR9o}toe-Cwz?0&~G$#4sarLF1}( zw^+u=n%aH_E(Iq|KXKg&$9-gWqhFC1#_WaNZw~g=`TZsyNflf1l4=iyEIeHMTtB;z z#?*c>VtBPsA&Lh_W@+RY!uDbHH%76A#)Oy)%qcSA=vEK%GLXDeVZW!g#CU0yKf~D+ zs;(@+Uu6>PxWO=(YGJt$`U zk1gse{dJrzP;jjLi(EX(`1|dz`*}qzCY#Umv1QArSOd-XNQAx6a8A!K7lJSUc71}F zGJuVL0#&cRQ$Tc9t8Q{f5vqHeF>vwXQlLe5tcda=452&YR72JF(4b=9;vvPl%m9a9vglrgx>-?%!Z@3LT2 zzLuHQH~sTMbN}Hs`1^)co1NL!d9W!Iz`4*(q;Q#WKUy5SG4PGL6dzqd9bFZ|{M;Vb{Or*DaxrISo9cr}{-ltjAkBa!x$%eGGT}{hts+ zV0~ac%CGV=NaS(6fs5VwH5;$@SRgjrUR!1x>zo_gA;%i3Ux-{AhZdcm@I0|21MTU{ zkfj?daQANg+^&8{vB-!OEK>D$xA#cpEFf*<-lC9=Ok-e`xM~6I4cvto^@C{ zbImgdk6LK-qqCd*@y8hNE*86@sQb|2yUlDn`?B-wGSacwd7ZVwVfo?q)>aRY#=Zle zzO7Q_^nydPFj*+U#XCtVsMqT~)%;YFRIxkK;YGNJqE%ZgTaZR~7(4A?uBvRcpT^$K zHb0}Nbucxv@Xf&bk5^Mld4vm(i3AF3SLCIDam#m=G#6Y!@i2`F%vUKSDkg!&Z zXXqZpm1i00`S$SP!*{$l1Z^ryb2483At+UOret?#XUB9cY{71BNfX|&v%U6$h>vGi zZ43J*@F`g6+#WL1W-z8gcaLQLxWbxJY{hu$#-Tq>i0{9HLr1UwZ+}9w#6K`U5MmN> z;A)E#=8qS1RnV!#GzUHh>X&EpFkn1wddS7axyJsY+s@9*w79WJA*yXYqpq8aW<%-H zw503mmA(Wu5n+)v5_arS~8np@6 z1Wn~&6IH5ajI7_@t*SN zQ11M(Wqnak1;V1>*I0y=X{_J!lG0t{fzMyU_|yEX#C|{TPTb)WfgP#_|QKhPvb@#9kjVTZ=i@F$VKuv*rEhjR)Yz{Z`zBtm8JQ?F z;C|s2W$x&tCp*FyhwZ(ZnC=y_h#$4C`ScN1N+cN*bdd{TF6;?yY#siJhLhRUp3;qV09~=19kl zB}1torJ0s59mx|_9`faDGnZyT$yy3`tl$gk$hcR?gvqj?AR5(NCCQUYH1q;tnQ9qw zwL2HoH)gRbQaM_s0?{Q%CF?lHO#M72!a~37f`r}eU8jxt8{zEQ3c_l@CpE~;tWHyX ztQTGqv!1*@mJd%FF^ag~F4TQHSC2=p>dBz_z&XjJr-5HVFI84nBMFR2$#7hO$Uq0P zRFgb*$$fcBZ{p}6UCFJXrmNG?hEgDm(@^Ki_1hwT>>D2rG6~yhiLJeU?C4Rk=SGUP zJUqg0u$!HmORxGifk!MhU017cUcZbgw~d^rag`M=G>NP+xv?X@4Ea(<%4GOM4bAaO3gET9cgarEmb9E0wHNf+ije#g(R_X1WQ5 zdafo01?4%3^FDfaH0;;#hcF(?YaBCpg>swn=U@(b7==;yWPmMcZT57$5Im$ymVcK_ zM$-*XW^7O}>(XCD%cB~hig+EB{Fi_DUb*)@DeYBZVo`Vd(4>%l?OA%k{t}#Ei+Y7a zUa}|s>t~4xg+}dT>e(@)9 z1>HY&OR7<928WZ{LBFS>xuaL*6vn42BNK5y!n=M0ELysKCv{LS=26yNB5LV zx+=29Cgl{E3&IilN_N?NDE+B)DC_xUsK?GT5eNMDbu4cW%9S|ZieXdI&tMtO4pVhG zF_`m~v(bkUnGDkw+$wUyl``6?liVf=Z+gG`bkD@`@PtW;K8sbco9&nsf`(O^1kr8F ztk^Q^>#x>KVp8S4JSK_C$n1E4+e;rLZnY|>Eo>|lRis^mdWbHd?Fe?bvS9SB({$XI z^IQVJNx=?bCM-Tb{@?lLbNP~!-qeBmzcy1_l~a3`Mr|9)d7=b^WLzTdi>%bD<$MR7 zC4M&4uEQC+v4uw6H%gaJ2GTG?-bL_evu>IX6|(~g9}093c@yv%=H~1;x!%PfL$BE5->uI!piHvU>!N zs;_ZpObUGsa7b=lm?SA!lE+AwKH|Cy1j=J-UL4wVm&% zHX!Ctx=sWp3fl%=erB+oqm5`s(buezE8w>l*3*?;edz_~rwlAH^0A2L-hsL$jcuhC z=@--0)bccCQLa&XG^!7rmr?9uOTp5*AMF1tC~)rWTiwG@H|z1{VQ-nRUl-Xd@6$V! z+suY*{^(8=xz(vbhr&+^b+<*a!Sv2=PX72I&2G&o=3=M2oBf`BL`qM_(=J+ntkokZ*X)}-~USwW% zAp(BA4Bmg)jN8(|4wIv2oPkb@@uCcBvAXT6adiQqMEocv)|kDC76&wJhVO0bx|N zYfud0jRe4K#dc1p3c0KNeawDXWgzrzkF#XA+jQ1E6__aG@i|p7QPML<%d)-aNvscT zlhS*Vy**Oo8Uwl-=w%Hr4}AK064R^^_igMGPm9vI@h`xm%=0^pAJwMj9=AHE+MxF0qm)6PeFpt34CxQbLTMhS4Q_%yo!=_^Jr_o<9F?^*$1un_WVbol)Di zl{r?@y;JG!&7$_C`-6FCaSN?efv(FabM>vcdT*M(;na2YF?3|j*hC~XgV5JF$F4Y@ zVuM{5=M&R|5tB~gW`px>vtRRa(KVKw(*23C8#iEXJH%V6S$et_X5G?O@74G17@BzK z9j2>n&dbXy48;;;ppoy2;2Fp=5nLRsOf)zVRzK@a<1%`mAz>Hc!n)x~{Zb2UIwn`sO)-QyIV`QS`mk%QY)B!8;nx0nZ+?1Np@w``k-jL)c1TN!+0 zzCRQFvFLTH#kX(|)n{@2tF&wxn(JzHYgk(?6806xX8 zXmxsbSFc-jt75KI>6(5+b5sRIuj_V%Rld1tiV#Ud-+r0P_Av}?CRv&YW+oCJ<;+LdH-71|5r%h zee|cKb({o-feD-9PIgc#%T9aOTdAn8wWt5pWxXjNOiaqb`yB>sh3{*#?S3A}QcGDd zs$MDZo9-%MorNYSa0 zEcB-t?`m=YwUhUkc{E;ToLt-o-8;AUQ55F!{+WL5zJ=MyVApbNYau8uE$emFUE zGgd#|T`%GtMu0}9zXu>Fpk3*c8rCu~S;oKO%+&Ekf_%qv?0#R&t{?w<%qFwg8yb(Y zX7g@p-)j#tHR*P&EfxwdEo@q1JaJLWuO+-vCb#^fix^`}bzC(lq7TbFX;`E~o@h%( zKn%AX#{w;5lT94nGmW`#ucd7HyRFaVXhL=FT6H`#8!!9z|0AB;Jn_K~IO!|uE zAZ}MKvOaVU`7Y`A6wEijrH(lKQ-U#j?z(q1e<1KM@f>QIw@Hh=)%5mDBfUM_VB)(< z@|$}vnByk5fDV`GNd8ns^L|7&+ZpP;`K%~nJp@GJT8JjQQZa2~2F1i1NmhFir`BiN zWwZ7uNF~~VeeAy_4WcN=rjMl;b7f=hpNxk%t9-x-=|n*c1?nZ*_(SOhf;oU(s6q+u zliZd9uMpE%bCDf>v7(1*c&r4MVeW|W#yeRstj%2Y(XaN97ga7w`DhY4hwJn!s&9$F zOF7H!ynQDd$N(@gBhXXJ*2yMUI1pr}50#7g@pmr(gBvJR6&WjW4H~@h>%nAjc}a>} zd10T#2ixoi{8^53&T#CF^9)!ILT+?5*tQObYa+S0b91*_ssW6>$j-MsUJ|?Y?O0L@ zEHPmqrD(Ml>c5Fu%Rk3}g@g8mz{TOyB-T3<5s?}F#zOZ+s;X*xInmv8U5Nr&HBJkB z;d##+vvst83}gG76=&S1zL&PWuCz9A)T%c7D18<7t5$x!{4yVBwKl5R=`-d^$j?dkrDP5ZX~@YGGAE)$@;CK_`W9`ZH{V#%Qz@^}Jt zQ>FGAHu)9;2&_af8*F&!t+sSF5MbfYIj!%C|x(rTues3**_q$U9TgkQzuBhY^QX z;S9~JQ-m6@vP_YzgcSGZQ~=*zcRO`mh-n;W#(f6#9En`(+0))CG)E*9JD`rE>QfcM zeFK?B^ExIohtrj@R>cMw<7$o+e~DHWQD0i`A=r4CWV<>=5@_S8F9xV>yu*BR7fmWK zZI`gsVbI}O&3t6ZniiG8oyM$O6>4YT(7*P<%zQ-QP{yFED>R%1Gz{IM`c z7k>X2F5C8@$Ij8g%zkkZuF&QE6TR`&Yy%fQPro*GS;XN*Py%|wku_issZlw{d){Rz zt4L)odwtq)`y&uGeO^&)y0E>DEw(39XVI{pVHSweDzUJ#xdylZ@+SSWcJ&>j zKJTi!N0YUCdJ6mT9M5-N{hH5jiQgM#!}tiNR*F2)JoaBYar*`vU5Z4QO8^z~l+WjH z_JOm=bP%0NQ(#8bvXWTP*D<;{yN>PdbItua59lxQ&o@_vU)_0N+4tedAk!ViY<_ZUN+dS6m3iHBT+7!i?TFKqAGNL_6bIAc)>0(bQw7>HLCAt8(0Ddwxar-jefOJ{u@#G2lK-4J416$ed$XF;O5|K^psVgK3uP;aqnt zw_r2p|IShVk11UrIXuNG)O7*ny79F$7I7l1#@(fV>m+Ssg+SFZFdmV`!7Rjm*@dUQ zY~4a#{6Xe))Nnc;C5@9$O@qV$2N@%3`{uOX;pN{ma zxOKzbNgL7~q@AVh)2r8bt#qc(Wlq@4wA{9Z+D^K?FA*!YoH5jX62iTkElPnn9g1rVynHqe}$X~Sgh z=w^z&8RbhcUA>9X4BP%j?NKHHo4UI$xx#;uTkC$cS!^4F7;m}lKIYvKvU`~qC8=x0 zTf0_&T{Ua?GWx>p$PE1w{xRG5>bg~9ZY}yQi!QWrK5RvSG)uQ$yTS}{d&a9*^Id3}Py?p17|8fl(e`yJzE%hj7OTvLslJzd3p4O!9>UqXobt7|U-2vItJYvfm(U zLaR)NaRo_KpG`WO5C50si*P$Z z&de_N;(K!1NS@orwfp8xXF?%P^=NdW?^K+Nv zEvYkU$NL2SMbL^05f4Ev!iBT^4Cnwc@YO)#c=Vv{p`iCpE~mYTUGc)-=!=U5+JAZN zR-je2|U3 zIzkWdx<=ma(?j*2j$aB)h#Zdxd{c_|Gbc~kKVDHU+PqY+9-(JITpXJ7*j~Xm1zc0= z{r-_%YI7;3cB?)1HT%o%M68J-v!H3-(iYmc1Wh$I>4ahB{>+@vt38xQjy+XYzNpG>^!_+a z5;S5Sdcua_i@?_((y#SMS$u%&S?D(j_ps1hn8-OzSRCz)NPTnUzch_}&FebHnzudM zB6*Y}@Z;M8r**#k{J78!jf{FLbd3S4u<5*asO|7P`uavt*?Qz*U~Rr}hHqp~L?feG3n?D{V7)0%LRl+e58-f$M6^<)RC=!;rKEKT6E1F-Z#=V)x z43%2#BE*V&T2$H|aQFyRd9nEFJ|vSoja4cyH0o%xCe^PmIGh$wJ^r7N9=6XB@ z7g(n|A`|)xwC-v$6#)oTB11Y%qued)$wgA_%9p;faaj(kdh)si3gB?;(7(1{5r{KK20X|s5y|U!v zNbkzPhs)vYI@O=$pl;p3tkW^Hz7He`ZF-rTh*Hi~J#Rjk z&cvXU_)AqS->KtrxXV&!IN>H=Qkt&F8K2|*DUIUy)jwbB(Lm7AsOZ1`N{%o6$QN*# z?y=SIO|*W;GQjR+%ghseu;c^t=TVV4y`;_#`{QiVq(aSy+IvgU!#|4vRGUTJXGI#{ zx<7G(QT`WF_4Ef`Dvk$U3U^uVPXhNPGkYa@NC-NuzZ={|cpnDa&o(o*RJE66$Yd4q zMZE4_!J;Cfcyydq`qQNxsQF*MQAOX5P>6%QUykQ7?|yd?8zs2a0$32!p6qr<`FO8& zwU@UT6yu-o#qU}^uXZ$Z2X|*yRa20)gUW29L)dSx#%;qaoZNe$!y2fy)Hznb#rMHf zkHX$!M@S&P-wkx_-v$ye85-VuxmlwXv@;l&s zI8O$NWmxj^TAMs??9yz3v*sb+Q&mP7`nhu(9^8!FcBhGhRewHI ziWxRLjWlkMetqOX<@U;`jED!G=W>uXO+~qA=3A_A>vn@wh}qPak|9aK(nAqHEI{A0 z%zB^;;qW&JENe>#{W0b|<~_hmJ0&>b`u*Lvma=uA@`kP?;TyY{jkPppbqm7%WRQnJ z`ieABH&jP?!QL;nD;J7>0d>8+xV270&+&mnLa^Is(R&XgWN08h$44T@Kgl<39#@5N z8Bn&RNK&-K@+^HiW{p9YeK17m){|a=2-HN}kq&)8`NtbQ&h1Oj#=la^MOq(Th^Z8( zV17<6GuN3WvbEH8_k|4hOjS`)(S?*BVH=mSgb=|hdeoycHH z7G|5^f%0V`S<)0MKBh>%Jk5Lwn2Ay(-xUK^f5`8{Ig!dkficqkE)$x)d+8y1u8=Cb zul>V?@Qv^OfbG1EyF^()hv_E-=dvTsjwE7{waQ^S(@KYLK|L-iI$znJO|RNg^}fyK z{HR~G^ZY%Vp+o91KkVi8@p?&W5fg~jugXDd`Yn?BixGj-C(+(+$B@qZ3#!{*%YD4e zayQk+y}P`J5LMu1@y^-Dqhc1Fph&?I#WiAe7@*i&CGYax-KjAM8~DIDn0(>RsfYTL z!sJTDnuM(d|^$EE% z^>#AsIazgIGQ9He*g-b|(_DSRD|FJpcjmFF_Uq|wbTw^RL|GvyEMS?x^CSevVzmEu zWuYEiDLJck@k-;@hiXlsQ6e51_IAzUOlsMg{Z>N>Wgjm|+~Kda93)=gds*yxA*2XW z)A8{x{N^IK-!#j{B`$sV$b%4dtX&rGzO|Ag#FiX)45%wWbV|tAk1<6uDvt`5^|mi- zS8NJA`Yjo;$1upnWYO^1EYLX&b~ir%_AbzxmXc1uK6IhqCQ}E^B}y;kUYBW&>d@=W zD?2(gmA`Pj&}UIeL8bqdGVnhE#`<)y#t4`X_H7`wy1JY|O}XsC_e z9*XYCL93Qgmz{86yeDX)8sV!GGO@AVnB)-+D_vvARrHR2btz+`x<7BqxpT@Z!q(Do zm8a)WAnsh_dO|~6s?omiyv!UxS(%Tdg04xKm4o!;`-gq}<#1f&k+T(mggBL!i7^`& zuiYg22m+wAfWz#-O17oWB`C|4D;nO}At)NcecDr z>i5}kGBXT7cs)x38%qm{xCE}Hg)?D)s_Ow0_re!bZ&HBUPbJG1dv7PJe~P^)`$;*j zJBta-b=cf(rK}$J65pfV3u{@)l8;WWa^O%){Oc{n$qq~?otzc6`4sus9>Bsu2Kx%V z_-%jIOS)<84q$rW&wN0h;4~AO`#i&49cEvZDnCF6l9LL|hJDpR-j(n`K)KnyIHBAC zeH96C|01EUhLS`oiyC|NGl0#z9w;1k1E3CI7MD!ix2Z>h5Uat`@b#JY3>~Cx7d^M* zucxEcI4TIz8+$)`^I^Ua8(ZlVdJCX2*@kze*YZ}r0a$EOYm!#ayMMg0pXJdijGRtP z0ucP^l7SJX`O(Y$1v;I@xBa55bTk|6ZEFC+*o{o#rAco2+t*ZYozO?28ofbp`VFqO^oNL-7y@uq&SQUh zA9+6q6O`R|R?`|qPPh`0YL^T!twYB$Q0~;!cRqsP>N2&k_H|U)qoPZ-L_#2gIdUC< zfYgL%s&1oSiCD~V8v`w8m13i?9MkYnG=RZ6%UNj@OqgPaM}UeXn4E>lW}(*nG}?}e z&yxpiS8_s~RBD5ygc64<3K!fR;0!)&8NPl2<}NDbv=_r8s0Bjf`7F{m)AUQoSN;~Y zsqpb>RgeSmF>vAzlj1j+m&Ea&o(fGYHnxwp#(hQZ!*W`kPEx-~x~wbszY7EgK9{*Z zRojsvx3#nKf2-O=%F6~jBOn6OPK$iqqp^D7v?4E6QHDmf9q#fgx&oj~u_2Atg?syL zFRi6l*kIm!9B(ZSZ0>E*xHBNKWs`#MD?)MxuAF3(>+<5Jy9ptsVGB<&=G?$BT9x!I zgF`fHb9dZ^;gR;vVGI847#Ke9zA%J{1pr9TrSKwsSWGp3E(1<#fCmT{ll$ge^%ki3 zC`jLrMAaHD#8x7e4|dj=R@m)+@G-1bFn~M2HKNR1${kZ;S=Hj%^s8U*C=wA6ZDx@y z51Ye(@cyZ{GX&m_Hr0d43vmEqkm-Int>gZ-)zy{vv_dzG+doBrgQAs^lam2<3MUXT zqWr!4B`10_y!-Cnu8KJRRRFv%Pb&#b_OX0XRPaRw#+D@Pq^x3M_o^{C*OHQT zh3RmmHp9|FGnj%^;u2*W&)_WJOzym-CL`MnyF27HWo2cX*a`rn^%q|e0W*~k&!Z6N zlyr>uVV`BCxA)PMR&}D`n;4hXDv!uJAZC`^M4GTRw^|^z1SnnIV^Xu^Z_M`tI(y^m zkTAS_tl{hgKyVh-gE*n!>qHpo>HvpjcJNL%0E^Yyoo$aZzBYcmvv#-40u$%FJZ4l0 zZqq{KIxrsIDw|M>4+fy63x$Rs7TS@Rk=JKo*0DXD-LsUI>^m53>a|gNe z{c(l?xdDW5hSfGg>!G2-uu8$U)q)!qr+Gb@H#o8t_@RyjDq2QflKp^ktb- z2`>u|(JLVLPIcsS8oC_My*vDHO3vTh|9b!kLMlL^^%FHP@aWywA*rtm=TTsnhB&CK z0>Lh|m+O3cM|6dX^UVp5aXH%W*C7!JZ=$cJwJA52A7kK|Ld6QHo5I$5ZUK7n%QLBv z8#fdQ2nd**h7>?3Ly;_hF%HR=dlYqiv~Sd`N-g@TbV5{jB6PeGpn9lTk6#F7JAEEq z*QTXliq;SZkM*gGR)gQdYS?wTEzuMZSH-wH{*Ao-V9qp$lOx-|cM^QI^YRZ&`mQ zwIP2n*Oc$LJj4@%(Y{SBD+c%*-GN*OJ|MY+jkGOANA$@P&qJFNWrY*%S8DiZg`5*z zogFi>?5BgdUA7e>7rvj)Me5Yw8G2<5 zKcNgSPdY>`Q+8JbklV7G+ugUwDoJ8-KdpHSBuU!s!sxh?P0(>rJ5$K&!Ey!Wy=1Gj zp^g<1priXN`c?6CUZw3$6(^*PAK|vzLW}wriC8v0jq)uGe%EAfGu3K4Y=9#EBiCDO zy#S+V;Q328dWx@RY$~DBzWt7ttdJp1gqkgy%+Pe~2xfa}z@a#ACQga3J={owZ>Ddzw@2^6Ky^BUwFN`?S zCtnn?@pg&y?xp!2cqLndH6cHwnB-5FSqCgLyt$a*ckF%)T)A#c@pKs=pg4)oZX691 z>8JlHF&gZSmQC=$dwv197eEFBWY%823V7{ARfInE?A%EV8pG0Kbe%|2CsRy}{j3w$ zWnUm@@`=DU8&zymQ0bFV_ zq+A(1o5@)%kNvK4(hfKOt#SU8MKJf-tgi013GdXE`(XM2{;FdsE{a!?npyR0rfQ*n2@pJ%Q?lu&#}o{815~LG zuB|tVby@i3E9h)$!MiW&)4^lb-(4JGEKhk|>|I(#+n!gU>torX@YaqL5%HjF44iq= z0)LtqmLJGIJ~Q2c&1Tgo7r0qvTMscF9*b5rX6^(X2bW#A zh^a6j0l&d|6bUYSOoJEYTZmF~nx2_%Pmt-=A*^i@0~+(0d{$ujQoVIXwY4maFx-$ zFh?|_Q{HpOrMa__)wDt`D*64(iY-%NBUSd@3-z5Z@}iZyjbLJJ+mad#P0AwS5gy$iCn-7{ZuF>g<~WML{Grg{Ove4$Ui zvfq_~dbPcVilCSGT}OAObs`qT}{+J)N0vX#&c`~DXa>GZs$7hLbu& zjvJ%pmYe|hZ|YotA3G2-I0K>k!@TF;#i}Ja$Y!MZ+ zFKu;!%SJ61Gyb7h`P-X-FMO8`{7YJHn9=DQJ?6ju*j6}EPdSm~A3n*~mgUX3YbBzT zpX2n6F1Uf!mmIq(^Cx)Z|Gj|F3%B@JE=z6xP7B0x!3&Db`=iWGxA9M?gTFU!X9ARm z#}jN+gqKevpP>xM7m6KNAN})u_ z>|X?@(tlt2XW*6Saq)&f{^_H4U^Ro*as<#-?0}lQ(z+JAuK< zAME|@p#J1tey^?XhpXVgzt-*gtsy!f2a9w}9!te|@gIKNEeJ3`Iuf=!N@q`Zzcv=U zAx54K<^NAs`t)b_Pf#87rGmd}DP~~k>Nj8fu@~=8f>*{QW}gCoeRx^<(hQ z_v`nrTtJ@KGR6W(r%!&rFBv7+#2-O+zk|WQ4&HuEw$U)yF zk>{Vf|Nro0|KaHVkujYFNu4QihTpGH_X)gVjN_>&_D{a(-(UY9{{BD1!QZ?22S;z5 zkpiR+jEGf!tJ9yh4FIpyq5OpY&mZyko0F}dY+Mbvar$&cCNzLQu=Oyrd-YGB)NtaG zCYIh#I-VNyEpA|wq>5IMOQ(kq{N3!ObA*K5eTx+jyoP$oxlAt4(w`CeGmCxv`NSu* z=@MT)cltDMVPL1r2opG-EXV^;&864_DR%vu^infb)pm1=+SZ%TlGd^%Wt4)ADy;h6 zPyPsJ=tss*(93J~^gz*#H_ZpWJk3?hDqgVP8d4l8cSx~@U$}ZldFcfQllOsZl!%2i zK+~?<#f^Ql%AwRlILr6Cm=A=WD5wW68-J>Um)5Iu{c9G03$D>4lw*C`F6~<5C4s5Q zvf~R$NL#6;))^qra`-|oFEm=|mcVN>d^i4izHSU><};&QfrVRSmcD>SP{xaXZ^YeN zu{@YNOD?8A@)B=hLo~59X5co?mUKKDH~uq*nT>}%X-uEGH1JP+!T58qm`N=`9jE&? zK6?#hmSc+|b_=@IZj0f+tR_E;lk-_a^7Rr0mlYw}Gg; z`-*=)GfsD_0Rb7Z7^p&;M&9)>S?EradO=m&%*7-&>NpV5K!l-?(pcJ)O z%s;X!F7m*v`^T$8kDFsYtM3EC;?on|#Vcxh)6MjZhfR(53%yG1HrO)EBiCQ3zV}qd zumNG2XQcg@?Gz$&(e?eI}3WRKU^cYSW{}<^+tDNn$vXtMG0$)Z1|QXMqjMw z5SpSeS21J3VLozsx-M*CAoZ0L@q^*CvTNOVZ_}i}ZGM6y;b%I}c0(qw2}E{omhYA+ z7GL=jmvZa*$+1wkhFm^1yEuleqYM-})kE~?2R2#A;cGSXNWoqHqvcOS>v5X}PCp9{74QZ> znFMp7M@<1&jj}%;9|i&ew^kH7?8k-cs;*C8Fha4;{eF3L&LS-3}KH_i-p2|L%BJ zt|$3^*!G0p+xWkB752u7E_xoWTt{0Uu6A8lp=4CDjOTVH1GS{M+2T(R*+DJs-MF{# z3*kQSb?^tQE zxJdj&|7-M>cmcd38KE@(^b5$6!5&uN7X+_4OWBU-Pk%6r7~kO=IMzdm_n5F=J3rr@ zojvTb611g2)uU2mZH46N)q>rC#JWtJal?oky_ZDST&De%^oddaj%Fm-9Al{2`4hC& zY7sIc=C)U3Dr|i4fh;asAoDxyLin(AK^ODpd5OI-(nBs{!O1sb=$?MHqgNSEI__~J ztd4LzL!&`1YMG9j+`{xAWSuUDUNj|U$e?+z`Oc*{3G`bdiR5=T+>e+ zlG<`)V#zPq$Kz$@V+k4{Ei?WReOZ#1);D|}dx~lnj%5NiQ58^KSr6m5ls`%oYC!#* zwyWOz%8HfN+tFd;94P$<6ZmZA2i{$L;8EW8DpV!?NsJ4Z+o;RKFQkW79-K!~ti2lDYAV0d(u;t@UqBv80u*QRK2NUd+;m*TnuZ8*FhR2OwY#yS~c99q*`EbM+QgiFrhZ-i}wv@wgoa%^sk*GaW(| zWj`HAOI@N&1>wj%VHk0EZ=8u%*z+x&u&2VaOuRy=smgqX!S!IO!CkMkWGlTQ2Rcmc zp-i((e8J&)bRvYku(Fn}ab*5`pwEf_dsWe}vYTs`G32gCwx1~K;oIS1@Ud|A`W_gu zV*E5kI@A|~>;RnLY}-UePNsv`6WkagmhQq+xhe!=bQVGnRcji6VP&WMX=Ex?h| zqnJLamfSg?Iq=$K7K(;`a))|EWQ=LT>0(yIjzsEHWNM~5qLV;G>mtz{oIy_-b$fh_ z9qERuiuOUT#jd%nLDTU5EZwtSZ(My2w{;|19*R6|z>(I*ZryM~mgQ-3ABdEhwOX%v zFsY;)z&cXvZ(DCjZo2qGSt)C zhf0XsqaK=>fzU!dq}0c|G9SG)@8>kE6h<*;?Rw>iWOJ=}dHA44qmJ149j7vH5{r4N z6@~^nzj&2^iS)FtnR0BFE8MjY+8RHyHL*QdM^Kgy?4Jc42{E;v3~~wgZcNL@zR~Ue zk#Wv0U5l0(hr~5Vu6wPAC)eyfUNe%)B1bl5a=K+9T{1;CV;DS+(-_&0Db&kCGOwWo z_K+yY)peuljnymaF3)7Qf^*VbjtPUwI3hw~lX<>%2S%jKABdr|sPYKrx|1lQ9vUjN zLtArC`1Wm3nOyEJ3r`Oafc1GyV(xi4cd4=OgPnO=4B zIj;UfkGad`SfA_sK1=@~%dz%6>_84OSy8UL<|gaWfnh|xaUow_FpwkX69l()CA7>w zlQzrLL`?S|4wOz*7JZS6xkbqH@MFxc!=KS8#{=^-UNN;LudQ?bA{VNj$H>+r%H54h zvJ^Zo@(hbnXb^}FW(-&0q^lR~rD!|D;|>Car)0Pbek zBM5R4u6-#S@Wis`1gCEmjeT}^G-G``;O+zoO%ycpy>L9=xACx>HNH3c?c81(Gs27T zNhd&KJD;us(jbSAq?sh) z9x)H@v`q_cu>{`pa5T%HxlhMy9(YpCBSL<9uyGiZGM%=JKiTX+nLLKB}39#VK%5dKkyNM4XG*UM4!2tR?&@gVT4)a(80z; z-R!4RoeOBRuDC2Nw;D~3ixAm_#XM2H;9`4h9dK56>*3Il=%oDFb?`Gizk*;-q5(tX zw|G6fO~u4@U|S6#Ct!yogf`iU4rn>VzOO4N_Zgdc;|X-gnfPNtmCqf9^uCqa zq5fijE`xDvylk!~=*hnlyXSVe7p9u6A9R!B&f0edcT&xpUdg%m*{u!{!jM4WYx4DL zA0<6l`W9?NO}mTlTa8XIh;VO7{)8iXQ;#W9G9lFCOHxQJ~?YR zUPL1mUKGFh6IThGzDkO3252XK%u(u%5LB%$%~c3!CQZJ4lT)9GhAX|u^`;!tg{ELY zv-+kWRdG_#W_e;S8dh3nll1;+PG(;-C%6o|sB((b?A*aJbm3K`6A2005+}!z5l0v= zvbUTDE%JMdg$=>GLfZ%C0*+aXG1GXi@Xq_f0E%^kl z*HM=_6Ajfzi^|4#=GM3rB%&bXimd1TaPnz7Zsj&4ToLp$^M>Lx;*($y(`}T2n|D zytAKjwW7)&!D^b{WQG-WKZG4d>BMX(T#q!?CD40ph}m+0~`zfI!69`rp?oFs?ENWPWA<~F&(4N-N!Qu z^H#}rBXck6!68+zQ>QuqnUREp-T8eCZz z=hw79Q>>TYH1+9jNOufWK)-ZK$JOQM8|(XNmLz-un^iyS?c9`w37lI^wxS-1pvU)T z*Lma~ClE}3C&F`IoTc)=J}tW22WC$IqLb-?NW{v>72*-QTXB+Dl?=l6&0NvKf<>eI zZJBWV!4n4(df?hPohNZEb^-FYyE0|3huANZdc4Kz??-fEEGpi!#q;J#`H$*FuRz?` znS|t7)(I#f&PhUCb0V3Avs;UNM`_JHvn-9YL%dld0x+iGT5r5rcd47)=The|xo*ov zsc3f#7U9;N11Xj$6G$dH0p*`A6~^4cf2+OT&5d`q3``A8WK z7lzJu@t(CvC=1TcI)f6DA}~u@?YK*=8G#s$!JWUCB$9QG+k+!zJFU6Nag5Xj1y71PLSDv8C{M1Rbu+qW7QeL zH|w@~7|)+imYVhvv)om%Z?fw}_3Dsb!qbb{)(yl^T6OBvZ5;Wb=8F0fewZ-LC)`-* z&3o9t>yUtISGn-mnj%q`UQJ-0Vzjft$qm3d3u+ zeQ9@HWAwvB6Z-1y#>C<_tNL@jxQuyMz!0aW_+?__8}@~>A8Op~nh>~9jNSG7=ro-Q zTyPpso`GL51=FqmzS%F7%$g}7cNgoggrkS;BE2#0dIH>m-&r#jh#aw2tiWNxRF~n7yqliPh}|eQV9- z&;j1{&L~Zio%M&c-!yS6%L;8_4MB25-N8U!APnaSjJLs5F;>q$GRElBMk#;W`&?e= z@SzG<$B|y;Y_fJ2rF0#=&?YY%(eS*;nWCid(9ps*5r-HfkY?dhiJWpV(08j4I+kDHn=Ro}x4}Te_S}0F@dse9_6#h!WPimQ_%*j_U4n zJebld$>o#Q5sgf=gSZJx7a2|*srjg#L1&^dZ`1Uo!x9SHt`&JJ|Ja{AHr=iflX4)L z-;lh*al9{mv5M4t?hQS6)hw1hxFv>Ji6>w4Lw<7{7;P|anz&g&ZMXFKyaCnw(pQ^< zUrE$A2#BvslcpMY2W5;!i+b*8?uiIUc&;ppyq^s9z)&HvJBG!+l8o;Ixqex!SJrn+ zK2gcVrE;`XTnEUD9^T8?zQNi093#K?@w^Z--|H0nAKMgMD*ME7QC9l9>(eY{aZaii zBOD&MnZRvGsaT^MdQpX#ER@vim`)9N&k zXGsrk6woRsrb}IPFZQRYb)$cpMYODLa+W=kk4e$o?8qgdJJ&=?B<4le-JX~*ROMmFb7ZE1`w=xO3G-Beh1{WT3h_#%JnZ zlrXMZkyFopuSj%GT$dm7jCl2X+S~tLw2@udKS6~KE#6%{1?nGv2DHP-m^}9BFLhc- z0kgk6OAP)wiO1A0CZ?}&B}*Ux^#ui7L9dq1{1~+IbI@mk*Rbqx&NwO^1g?MC!)&8G zRXG}w#;GF_zc60e^{`$z*^#HQdC7Qjz`ZqeFl2r`I#sJg;6pnXm~!&f1EU3!K3E$iySB2Y zpRe#@4ezfam*nuM^Nb)}!^tilGm_-<36%>c%expTe-DzGQWWIKf;5KS&>EbHMyu8( zZuDHuiYqL;M3iQ;gS^%#oZ)sXC=hjjq1ZVoJ=aWG1dM4wfcB(krXMxq2ZiIRgI=W6 z&Qr4v(Q;?F>}A@Seovh=u?i*bHJZ>u!_|4bFlK%jqxhLLIqPP{YB*aG)N0;y+IX?} zT9jP;)g(%*FP;NWpUK7F3K)6rhIrWCJNB}H57(^vsKzllGhLqsf)Qzn{Pcql_dRYv zgX%K@OF1Em)xU|B|Dp4Lwfvn9VEI0?m)?B;&5TE!%&byTwK^T`+{PSL4FL&5xcxq#oSygVMxGBCa=G&II5G$DkT* z^3rMTZZ5i!4Yplq<1%8sVKz$B-y>DEFXd#3X`EN_RXy^_uLCx}#c z>dCEK^=KDnh?sZkiDf7=&?Z|PrKHxm5nBTbilxWn94eW~aeB*(a|m1;#_j;_i$SgP zG5wsa^AQTA?ZOn=kblINZBr+XyQVbGn$8aHPm&4rH03BoE6|_&7|{-U>5}ZBJ$BFO zrZ&@ojsNhvyPCZcPqgs#~gAv9o!S_sdCk(Q!O$;m+jX%6k}}9 z3Uj&SQXg09RvwXtO$B08+)|Mg#AktU_{1t?H0vTNgXs})=}4+Fy20h5k3nHOc9U_t$y|2HqpNS4gy!DapJZq?9@<1N_3WQV@jR}v-*WFdwlYK+ zz?aZ*n6iZzx^J;|BF~B~II*(RyFDKegs?4vUi2W1Uv+|fDM8ar$X|jhN04&YEZE^! z!WHb&(!ynWo-y|(pKmgI_=Q_wfN1gl8Wn`}ayeXyUX||`&cwx5xZ1(I%a{)!e&YfrLwC`s(_ z-X}?kLTfC8|Be}uIKhR`eW&zG;iQ(HFk#n(inkR4(l2d=UzDu~SUAg})@YVS!E^BN zn7Z+uN=;mpj{L!FDq|RE+TIQ?tgfb;9jwr7SukMp{`iz&iTd@x3`w|*Cc~H)n+J-_ z=iJyZnad?X*6eZqw6#=6%g6tD*~I_6&*f7Yz^z|b0 z_hh!e^gSSmsCv~W*p*i|bnq4{cyt&crZutVpYHnznH~L_XETObo!Mgpr*j{a9f1ItJ*QyYoy+;;8BVgMm>2s zjtlyYISvYAHi#uFBk3PW$x_b8!y_ovYzGegLu1%^V-|~8v16k>E}G~1nHROKM53Ix zm$ckGruZ2vX)B*Kg;FgY3q1DA7#gWxD;PeCI_t;o9YDY$MMNi)Z|t%pSVuEk6F!S;@xToxeMVa6pS9@gH=irWi2Q;w)^pX zW&qP?Q#&@gXF$vO3(sBa^Mir^$Q5b4zSwcMq&Cw5u9Nr%^XUOc!sQ1yVpoJ^lD8v{is3WG$sYW&@WAi1hgu~Is^!I+y zuXxZ*vUMqiu<_H$nd;7d9sOodS}k>NTn*+3u{Wz|Ggt$RiUXVKC6y z43{G|qFz0k_PAhOFGu4FRNs;I%9i;}!QPmE85fLd_9A&q=pl{oX%kvgC_L?p}54>es#JT2TaTEEtWei2>MYH&Hh?Y@8UColuvxK*=xDwk`S#cgEI z^Lc@%O=tezGG6R;XQ#@xbAdu3MT?{vefy5?4X@oYuCkE=zF=><5OU^QRSvV)3YNUb z->91*Eqpw}h4@#zSLCA~-nTMv>kv?S32n6*eu5}HPl%F12+8t#BD===vm{xVo_8YS z3zg>hOg|s2%^GZv{5ppkjXCyM63^A&65YS)EoS0Vt~}FbU}-~qIXnR!VciSe^Ii;j zm?cLM`*yb|1|sln(5MxuXLKH&GrVtLZYwO>VPx=_TkI8aIK2JZ!Y)_rtheY&Ei6%Z zqZjMkX&{Ln(ziSc9dX2#nN%Y@tE^nQknHaCV^q#-yPFEO1(hiYw+Y~~Hx_a4%U>eV zy6_>r>#E3wmXU7wzD383a=x#S(32hxysntT-g)942@#p%VC>4iYc9HWA4W&-QEfl?4?thjynBgq z>L@&9s;*0|!B+Po5@QQe`e?;+EwG1(_I#PzI1H?HHRO3mp+# zBYn4wU8``NmpK|KSxQ^mdw7bEBu<5}oOjfah(*Menq+ys2sZvgld-SN{LkWu5sSH#*5a1A519IHT^>(+jS?T?JypVV$iBR7y*%ftL|(`)L^S z`cKJdBjI;Kw}vNorR}j>13xMwlsb!x0v;&t@ObSxa$1_&coJSh8 zcJLpM=@)*?r@|p02~o8(cq_ik#k87YSTCXj4ux%)F}gIJc^8@=KW%1 zQ^81h(1~HBz9H(gP>NvS1rlt&!-wC$wk!;BLyCM?UoID1#kFR)Q>JLS*y^J54fH#+ zqkrwcIQt68UQ)@@Xdq`RT-$Md=`e2XGgz6mgE)(>>3ogz<^{cl_45-pn;%K^_FC&D z1DBl+;a7LyE$0fHFye=TI>+*lHJ0~y5jHbbR3e^lHpC1nk>q_d(=)0=d)FSn>@nXL z*{?MuWkij;dJ6Ba?eeXlmwg7+_bHy^oCFtr-hoSG{YL%jJgyCI0qgWoJQUAn$5^Lu zX5WHgoUDc&6zCSWqG4t`mnuRCI|&kmek6YK!+MDxsEF?EO&&kCQ{TS4E@g1+laVaw zpx=Vv95A%hTckF7zB@U~*E{a2iz{$g=XVCj5asCl7Rzz52`ew-^V*Ynr+cx^+^4K#A7zNiI{G0Jv}(etQf z^VmKzirB*SiQ7-N7~)57n_LOEcr#LTc+iSlM;{&9e|H*q3a$n^$UaBwK!h58LGe*u zC;B5n0$khqW#^CxzD_)U{Tqcz?@VEG;bPF5J_w(44fQr8^eohSy&A_s@%9)5#Zv9j zO%aoCdKdNg>)$r`1%!UWlpm$3u`%(l@Pbj>rH+dNzewIJtZ{!HtXAh@tjsk^<97Ls zuY%J2EZ{SscdWRi;Xk0Mzi6m=w_nz8&-v5RAWUE*QrlLSy^Z}xk)h(1nuU$rl)hS# zVcEh&N-?31gc3n0Iyt&ylt`dSPpJ0c`Abs{&n^z}p`VkqLQ|)w21ZhD1IwBa8LE$?zu zhjo9;l3l>TB!aW=kZK@oWqhYx%=zi@K;ZEHxmqH;ufi=I)PcfCvNtkva#482Vto-_ zv|%sev$AL(Sh;wBGn{Q@@kJ*NqV%XlSMeor8Lbp71I?AP^n;SdaEb6#3K-tWe zTlAD6q~OrNX&I+UvG-%%LG`H5ZAO`?dUB_m9{;q}rJw(X2+hcYGG=neenseyS>(?- zH=VY;wmDrDIqmHskR715#snEF1X0hDqZO#SD+47~u0u%Uf`k5FPV@1LivYdOA8t+% zKTQjC&%VW$Z1s~thr_+5=DLiw3#s4rFi*M_wOpT8rl$5m5mP6~6;Sv0br!=6vIA!NTIAfCs$87^I0`tLI#Y-UG(vazw zdq%5S>C9fC`la}-QeiYr9f4nnyDdqWWKQHMwqqMJ$Dsg^0#OYluTWg=VS02ypH{*c z0fI7(*DHYgLZPB0Qag-aF}`=(y_kN5xj#ZgSKVBA=M5K6E(qU!zl3VHKasOu1*dbiQU@6O3py(E1^xWo1o*nb5d_}dSJaP6us=V8Q04maL&8xJMC&;kZ+oW7aRAjB5^k>bruXu#6X@Q zzC8kT>1)^bRV20i5Tr{Ouk3K~Ns~Zkrl0kw$VxsgxN0;#z^da^Jqj{y7yqpW|8<>| zBn^{zn4_k~fH-p8RpHdSJEYFf-}9eEdztMY4chpeZ4k*mWTVyALe8!ekql2@l|~5# zv3p`Hb2vJeVdQbD;|j-5H*W*a!n6@bNiAsbz7Mn$!#XO2WY(NLETZ0A;K4M1UQ zP35HcM9NX%LbpRCx5fYR205^IH?fl@ZaWLY@7^N1z;hnfyr^jNQNrzLzoKOV3(Xu$ zEt)R!xw9-lee1~lvAS%^Ht}FBu2Oi!D9AyIz8opja$^q{qTqheZ`9wsuIP~@z*IF^ zsd3Gx5*7Pxwo?{mlxOU`j5O&NGn`In_n2<{n4h$Kl8{6Wk4=9KJWVV5uCEArstSe< z-?AuQiiH;;R0(-%r8uFX{~u-V9oJ;Kbq(7P0TD+;rDFr>3eu~Jg|76DN)wUZ2@nAR zMS2TOumDO6O&}0RC_?BF5JGPe5CTDJfKa|Gc+UHt^E}@>qyLOEKQqaFUuEyL*Is*X z)8}d92v@?7za1jSs{(*USk*|i;eJ%M!Neu$&zp{R+|fRIf_Zjet=!n5sKFaZNa8@L zW=kR3o{7FUIq{ms(OEkxt;0bhr3&U(T&D%gKiWF2q9BX$go2;xAvd5!xv6$P);5-2 zKgwO6awz8yPqCPD>5gY!UXwU`Sg~sFB7<(s``dIa$vrmtD$~aKMT0CKUEEN4g|Qr_ zMo7=7V$O4S@^y*>LB+NPQ2?4}V$ZH=?0W>WCP}jcJb|-Z#!+^$lcptJ54s*M%$1E9 z)>ZBCIn9aFt0PnO{Bg8DvuFKk@ax7(4FkshW8Bmy*h-@8ZjJ_=M@8BA3z-Y-4vyA* zbQiuUwoMG}5OG;O;%ekQ8+^xu%G-u3r872bto2)z^^j=mh|dR8D(RHjb?$<|g5IJs zD!m>JK5%54%@>1=>>!^8cQhJJ=|%0VexM74??37JxzW()*I}#65fvUDe_2)EAfq&6 z-hOMK_vwgHoy5246YhCEHKcf$NR~?THG(L)O&Hw*ORVuevzio8x8yq0`6|SdfkXOP zhF0MXg&Cajo=h#UF!R_wQeTZ{MeR7k!MM0JmsW1+qD@MON*fH>vU{`5T*kA_#ue>^ zs(sAe2--YtAQkx|^$|OIyt>PiGd2_Ntx6zRc~>vU?1@N%BKk(d_b7$El8S^YdP)uz z@CS$Ye4wjPR|DiOH6(R&;w&3O^$GNt@{%)$D}7y}^28=xwYVo0WaP}o0DT(Tp&@F0 z@BLf+7WqEf?d_guaOSDao+?ciEpc<-W%A8HdGvxb$rW`k3)3|gI@_I<0NQ~|lZ-HB zmvQ4>y{hze#u~7jxg07Jg$eloN>CH@jRL+d!bF7DAurEvV+I9YA1wA zTj)v)FCTfwiMUp1Vl0W2%Rc{vQBp!C6GWjb&qn;z(tZ3YG02|@$?ok%y`NJ+jD>PdEG^& zpsFshf+wQ@p|m&zYmcb&zhz(*(-3#qYLsKk%DAj*J!`3J)tSJ5H{Sjl{VLS&#WS)6 z(k2Bt=k7~|Vce=o8s8|hQf;dX3QfCOJQJ~l)LKu`>h7ks`Ka+mx^+6rrn9(ytnGa` z;dzK~s)V1)sJCNmjT|Zv?^BdqI@7WpY(9YLU0EfL>Megqon!k<9kl(U%jVOHnQ_Ha zAuE-$3?e-;!|&{%GRF!(5V;;}(L{0<=k9tE<-gLDr{qBwdS|jMU+vutWyziIgS_M! zemb3!;WbF?kh+BR!n5?bKYc~@t3xMxX+cfQ$$ZJ}EqJ$7I$qxcxm1ff8K)lfd z{f3dnh|~)Ky=iOmy`OfU%Ni8KW~!{2JE<3Tj3h#lce2&@zA>)%&K1(Lev5fFP@)3k z2%4V-oGI?>#JI#=nS}W@Un6(%Dv$5Y4{V2uqvUSq8KUJf^*=AR`3V%fC}dhH(?#uZ zC&Je=_Swk#NBE%nt-jt|o$Gn)TKuh=sk_O(q#bT70)Cp;q;}Vr ziJt|@_i9S4LEl*_&_J=HoR4An;?{9D3TwAa7fA>?+;^5)etVDe@qU)s31teAxaco4P9F4?~O>}l4XhrL-5 z=GIP^-!e(Wt}*id$JgLOdhR|Ny5Wtbr4im)n%p}uAwTY}N43Tvy3NPQiQ(0j$ z@7V97Sf##YwmS?NE?r6q9LU6qO0-m$$-Ft>QWJJN^8=H-!~*Jsd2_&rkMq6nIvmQd zkIE}yLnL-sC`7M>jTfomp?#h%4mI-qi8Z3?YlEL{J~ltS&es zi42FVX;hXeY1o*`Y?-Y}X8R2;$n(1BT9?iVh<)85ZVPI>9UXY+o;#MJnGPw+JdKu~ zTcidd9e)C}uO=3vKen4SI`oEoacXlSZ=Re>a#>ZKx(u0iL(Lo$e-bz*EUUxUAO99_ zS3o;&mk%S7Z$|Vla<10?Jv)B#o)d^qXf)bs_T5XpK7eJu!^ZT13!|mD6VRs*MUhNw zl+KxeQ0c!e*NK9zx9#pw7esF`X?=R=-}Te`lX^ougtSnRXG@Tm%Afo0o$utFa=q>r z;T?zZjIEO)KMz4flbE*TVS={1_k;AfGw0pTSjF!7kJz-bvA9uDG#}pDe|#CVJ9lZ` zI&?#W;E$O$qv8l%L(rtjLHf3i6B+6AraKcvpGfY0Z;4wM^#x-yWW2nybm?XDn7)KZ zWoL}BLwD@=IGXKs*RdJGj`<1~x5&4~LQ$MMV+U7jzLY0pbM$&OQ^gB*AeT(5J{QZW zg|Do=7KP;7^}0Ucl5`b7C%q><9^#bw{%uxteNjd3j+=SkPbAlh*$48Q)0{I)G8GU3 zURkJ*oY6tF=||LhR--sqehAA|d8hH{H5s$~T5s!05lsiO>!g2S*P15augUr78_5M| z<1MO`{!#3Y$T#ujjcWrqQmg?KAD%ioX3@6@Qw7FcO2bzmN+~^Ngj%z?n9=6)1;3{) zsTVDpA3YlJBoHn7#^zmJtk#dC0%kW5O-|BH0Nq>S0`9$%b=N3S%1p>}wT^GsD_eT; z1m@znM(R+wtD%zqta$H2oHqlvtV5qg>80x&tb)^&+2Q}spjG)Uh2*O|YeMsjAgt#w22-# zXt0Egxc&d)I>~Sd?J&R>G{$Dnsm#BKzV-={&p(Q3uyQ&^@eY5>yo0|zWdfc#kkR$f zzAO0&!Uql%DbX{8^2jGU4}tb}2|%u*Qsp*FiMczC3yjH+a(wsXI;Giv=j-d0!PiTU zsE^X^FUbWu!1P->X@u^th+_4z?V=?fX8EVpGX@nfq;k5w4py{@_W!-Y|99ypE&->P zM&w?kqP!tj3}uN#BtPETe_<}OBmM&=y3>LN7iPdz>v`aJHeDpz+;NGKFbmg{zw_X+ zxxmL}`q&_Z_7}#I3;Z1O@Fk(Y{%Jw~bIof{aY=Ss{MzE$%i!J7meL+d|F|dr@fG)U zz-GT{?P&RBRaC)hdW~p~(*Mp9{cEvKq5y_%AdN0}?tjOBZTRoLOn?`KuoezyJF>sp zRXs3z5Z+Uy++dCxqm69x# zgy?IFHH_f#e=e;ii#+SUM~e2{omFc|5zMm@DApHu+*R&0UhMb`az3iNzMJiQs<7J0 zM%7H4LKOV3m=--m)&1iWb0bJk)e#;RuXVenN!R5sjP9})FjuR_3jQq6NH|^9Ybkho z`FotZoQ1RL?6=BP0rTAg*K-9=M++T=ZREan0(tNhslZsShweVfDM|n3@4PvNd67zY?nqb2WZMiTpL?wVLawqcNL+S@oDC7o4;qFnj-(g2 zRhCSV>_wov7s#hRI}Qf*augx0ZJP`)oU8%+@x!k_?Dr1&FRmt9=SZxd)ApM#5CO$A z1o_D0s&i!5bZXuX(L@4)ia0BHHn_y2p>hQ)FmA+U?wf6nQ9+4`r~QBzT7nn!a6E+P z7BB{AC2ytQn%x1(f(l{cdJ~YA$n;kB6PTsNZaTlySQ848d z4Q%M}wqh+nH3(t4p#N)zf8*bjHMk7Hz6~RkjXrw~1hM?}*ZG*3nDk<2{AiqDTY|nRFV57nK&P7I(2^$QIzBP= zBj$sMb2oMRQkA=%>fmSFsZ>Y3=8{5~aYCkMODR@7rUVxsQ@j`j>(R5CwdGVN&3`z! zv0MQw#Sja;qD&m$^o*=8jyakSjmhkRG}U@@3nR~EFupyOG*|6{9r1jnr;GYl?%ZQ- zSm{!>lMecJUeq^BxEppx=)&y+yQ+d=``GihwyuQk&aX(7>|ohDy^=+;rUIz>UWII< zhkn-N8zRki2lk@Ax52RRx28GWelCqnIcb{#Z@|o#)?shD{MN7NwqgrRK1utgL)C;& zB{>~=>0Kb+pF~Xlm=XI@?XN!3pQ-s{MK`|PS+sgJ*|kYpN+9=O^l%?7GBEH{^$`&!Yb@Ff41$h z+N(QU^5eDe8|Q`LM>U1@!E^%_YjeE`U@N4D*4jApaQQKu(BUgcfw&sTx-v)%T|%#! zoEFqmA{OejkTzXToP7LIm!RQstlDR-HT^2$XlvOZ0$GoU?8rbj!xc5WWR=EG%S8Y( z6SY(A{W~uDU$&czndTB)iH5QGbUAV>%8#73WF}YD%FaZL&h33FX;CLF zT(cVZtw4Kd)E5k%XBJyFO}^GcIEg3>@kDyeUNOwS@9@cfW(=x|MjgI=c&pB+E|4nI z7^b0BrN)bmujN{%_$3xGj`$gKuNF~kemrtP50Gj>U>1DCajCX}41b|pA=R__NSTDZ zVc{yrz1>1&sMRv?5}znuVmvIQf*{{jD>^*cIP?7#&s@I+I2;W*ahBgdb(K6DaKZXp zXSvR-YZW|Y0^!e^TsX^`L%gteLGdh1yMCeSL;1S2c#6d>#K49PN+y;rPipV!r$u#FRI&C>!@{2is|^`#zrICPi~5ACB`?3D z-l^%j_(7+!kLDT`1_~O*R==VaTh!r-PVq`UkqpAW~uI-KD!-J5+wGv zZi&`&^t}6=yp`iRoq?UAk#VVY#BF{r$ARV4>wbO49VQia_hi7}@{-4me40gO6}GyI z_j4r}*dz_6+usx-y;3+J_@<5rk$E_AzbzeQn0SGl!qu1aQunU&J0L+*PnDW2`VVRR zDI;&xX_(kNHBPGE%|kMN-*B;E-)A?`(6E(e@rrt$`d82&I%?w(Djz`y|Jz4uHWD2=M^OHbi3DJZJ zz46PG2thgMOOYV**N6S_^b*aB#?T4{38MB)OWfUTsB9ytjg#md;K#V@L0w;#va!AS zI& z6VvV2<`m8g%i`Kav-!aWWjy_Q&trb0Z9wyI_pPH0(Xp3R`z@djW9Ju!JePG``&x&L z0*q75f9hkzE)8WW-{LiR`<6}0M~U`yv+(B1RFAp`vf{B-PZ_Mt4i^hEF8dkow>7}V zEW1IEHNe5yvs9*DKk5f)Tlqs1)(75Em|HP(ps%&q;_lQtmNMA}l2LCu#BK3$@KjGT z7!8_bOfuWIQE6Z{xC$7Ewr~youn5fGA?Kbhp7vc?P}%(Pf%(DB9%1x$?6+NWb@I7V z`AwZsFT6cgYV{l#2pDZw@DLS}y(aZF>}P=B`5-*Yy#Mkm=@RJPK{B&RwU_evtktZ( zdH?wth24H3?Dc1p1MqiyI5S8uGhxW{t$@Ksa4Gx-!Q0hlh)kAK0IB$P!_uh^iBKu> z)4LH{>kGE53u9=7PY={UMoNRAZ>;<(w$W;BuB>k-h3K8QYNB3XgLu^NvCOVw%zuba zJw;q1Z@nZ+SI{Jb(=%^({6l&X1Q>kF`i*PrD_JY6s&rdZRGzE~4&kTy^}fiRwor&4 zub_z~bhBT@1=(G8u{Uy03we$PWmwR&cmAH^{g1$ef{$ZU6ibdrl69T?#%HPHFheK+ z=Au)|=Dcz_?T)R-8FIET>6=VcQiG$s!{=jn*|y-yf`eUOew?`d5Hb|Z=*C_KqcKoG z7MU*FMcZQCn$}Vp1-TsmFPn705uuj{SJ<>P<#|rVb-pR_#LBm%@y01xMn*p~EKy0! zHyr*L)~$~|OwG_d(Qx;B^IrrjK}Xc=9ZEDI>RA^koau-T=&bSxIl+FZV#MdQ-1J#C zM;yFN)&zgvhLhfeG~bIe#6^ZQiOIqj67+{!x7Nl6{8sP<64Y%S5@VKnw=;qsHZYSdem5kH$OpImZy(U5DvD?wf>wm3mYzX zGP}fU+>QP2+{$iHq0tMF!) zCWBMPqdoVTe{mI}GO<;c2y-wM3W+=1(8$78MZ=W zlCyVC<4U?~1hb48w?^7Lc-kdDUK2!gnzXx0u1>+kfVCbmPv-%=3!;;o8DGZt>8@q_ z6LG0FuxkN#h{E?Jsf(pS=i_*1Ja<*-rED;~JL|RV%9_+ClPUB#+TokmpTQc$PbT z_m{J-Yp)FmZwys5v8!CTZ~+uXwRX=BG+onU_2hiA zGM!<$`s3O)j zMkeO%jf#=xW20bTVa&OiNS2*+-4TdU_{uONkjB&AY*SenaS{8=Qhct$gYuH1BgETiNjJk zZcs5?`eWTAdxejYkcRFob5)#!+{i6ZRuME(xIr692>x-FvC4yRnY_NRVgeg$)z<7S zMQwalf+H>@j7`6_aHjXioEBj4c`TmE1<(NR59<}ZKkx$qmV(jqfp~L4Lp}iAQI@Ok zZc(?`SBy^1bDBE&4&Lpt0=6Tqa?pD!A@O{$e%qLt1{}3DcZ4A__4QE6l!RgN<1F93 zov4x)+V6Mn+(}VS&k7k3`^I59^ibChx|vY^Ap1$u23&^!Ue|@&mvnP=x=UdsNlLaZ zP9OU70hWxI2q9Q)6iK`{!TrmVdN;*^DLE!dM>F;FO$77w3u8F_`>%waU>8Y!FK|{k zMcB3{`_KGbrx@%c-&2f-7bu%CD3lCuPEs|%L4tX&oxXGJ51NEuQ@lt&Jv%k z4kA(WbUU}gaDST@Ca&tXr2FAJf@nuZ`X~j1g@a@!M34hcuUmI&q&>OfVet?Cev~yw zTRbHVnx$~C8u}-}W96|+$Z@Z;c_phNdG)JV<&;=$ePK`%2@WBNE=Mz2A$iREC>W%327chwq5{l4@)Oc#mS_&ORxaA48ib3h(JPp%l)m zyxcCR2x;z+7&&L*RQ({%x@2waXQ`C`xTp{COtyX7RAd9|NTIl!PQxHA9wp1cOTals zx9DX-3+@pe)Kjj<-=WX&Ub+Y<%A+V%cmQD+dv$AP38i*xN?vt&;C;XQ50OF{S)R zk39)EX!Ns@D&0s!-Bbp4X#wv-l*(vXu1G2=+f~M6o^Q+-SznXd{<$7kdAJ@&r12x4 zh^L}MJB55R=onrosJPP%2Kfx%IyDBs&N^IoY(#xW#_b6|U^B`?cj|Yl&Vw{^?~}Uv z+a6X6AMQrA0BzLNNY};8`|kofhnDk>j^^FfuPoGRJO|*WbjH<3v91COCeV?x?m~0n zmPEnSAFyL!pEM}GH~9lU&0lPLBeca5<@NK#zHhX?5^hweGQQnUo}*<7u&v=-WkO#8 z#lUwu7~Me6mSSZHgE>3sl;!0%Veh7!hS$zJ(ZAF$G#j9aBK$jSNPqg>+63mvuyD4O zH!1EtWxIY1VN>t^LEa!XMzMr8(k{vHRtHh9aFlVmer5D+E`(eg5MegZ zL9CpB2vm(DG=7YJs1PtL`Bn-Irk$FK3AMX7=w@pL26Bq{S4$xB0+txKqbeFYU`Ecv zg@YcK`R}EP-aX;HHMR2>oX6~r|*}~i@D$Ugy;L=J41zAsX1&~tvWvPmmT`EN4WUypckS*b_q=a(1YlKA>TyL%!Pp++NREv8>b0F zMm@@nCKx;nb|J}b9oyys4QLhUeEuhE@wXI*vLTlt6=tZ6aEV{PVBIT{ZL=KEzlPW( zh*e{lZa3YKSKiO)WS??`-3SuWt2!~Y=?xuzamC#1nC!E3 zS?~8Iw{8L%Ms<|@Izsp0-u>~*{RCI})XRn?*2U-plklgDG6Q0tCRUPr6ygDa3GeFn z9I10EP=xn3LCN-ceq8i0JGy)|2ZrUt0dGMrC#V~YnE4ZftxR(X$Wf0z5R0C37$~)Y zXDqhV=I+xX;Dw_9HdoGOy3RN( zcU15niu^hDbYZWTl6l&6tev|IPSto_(DKt%4`8Y97tXxcgI~ApzCQ|u2HI!?sTJY& z!eE(dIs|2P535<0tNT3xW*)1SEIP2r1Hvy5CtIcIuKCv=ah!r2c3DxEPR!MhDp@?N>ivYXy>*Ts z{cF2Cvzn5g3hIpj#c;5`7MC6$`h$?5rUXblzqYDXvE(X;^#C)_+3+H!t#1E0J+VB_j{xE7?%u4wiZT(oLSyoT&o zMUNNi#r5TOUlSYgT>U|^>=<-U&tVbj%bm`)A4A=;Zcn-l0{?HvrBvy0<^U$-iFn`Y zI^gh0P((y4+wc0VCV;9(3Le%S(i2;2_2^x0)#Y$;jI9`D6Ob75jg#vG17S9wwrJTv zlI>L5kc;6?ed;qgMhHjnLg*_cEP6#K~x*i)4b z0mCwoNQpTi6@XEl?Jf(Q*^c8z2?x`ib!m#^kmAemU$3-h*`PX7kZt;2$=Q3pI22GJ z)M=BT&{(GH@99bYSY;oTZoF~)&98Ye@!t1ha^_1 zvdHRcc4w>O*-}p&Ql_MUyQ?@&{v-wbT=8mvB~aY2A3X)x29p(+u?l3Xz1^MnbvRjX z7gYyBf{^{-5oA1|c3RSFAXEA7DDWwj@@E6S*esmK*W7lL{}EDOg#oP_T9_osOtAOR@-{p%ak}2&QsA&!(N6n3*)j(mc4~d5L|a7*<$?2>up_t zCz{J!Y1P#?W42)0xUcC2*g9n}HY|QN!%rTQdu%Vn#&_=9t5^kdd8~nNAzSGvo`c}6 z7AIZy9jHRSLbAa=wdzX zA=m|A^R>S9Q~%MgErDO-K}p#tag84F|5rZDwfg1DV>voGps}c_%I*eU({OuA-=#TM zY0-f}_P%nJCt;rBJwR$Opm*4g?eC}gqhIWz{GzlJj}n(Z)u87O0e-!F+Re8t0-SO_ zF92t7^}Aov%L9k%jvR}mj(f!RM_+9Vsxvf=NI`iwxcUx(=GTd%ep_uIB*GvZ0f@-* zLy7}%(0Oh~1eo`m6!pMAC~yDQj|%~wY^e>(fH-ty^7cbYSiC-uoq`FXTl>Z;1~BdE za!AUWg)s?p7he|?DoL}V>^dFO;J#g`!H75NDYUXnC21{$s;n}&LK0bK{7-UAW_}p2GC;#); z|K?E@eQ7B#CeHw~jJoY4+cL@`beytUT(cB4O*ll!K=xX^6!q_0`A>?Hq0KarEB_K1 zIJJPs5>D>4xqE0*{>pDjl71zYfv-3gswf4ts7qIz|Jr|}7Hu##BWqaV&?M}AllF_Fwq9792(5{SCLrw1JqYs9TS+m6eZT>Z$CH{QdZ9g7;*`gag`QGs09u8K}FoGhFutAeUUhne>;q zdi5JSf>@zBo-}|FI9yTs2j6_`Qv7oo-&Ct-os0<0ai3?AOk=Jzk(i1rn0=|#y&GWf z4?C92{8%7Tce)lLXDZ1=slg}*<8hOJP$B;pLmkk6OzG@T4@Xzh2N@J39mzQ&Pg4Vu zxi{%tefM|s>KQH|dB6#OU67deKk0@XO9N95ksnR9#ugDUH!hExza8BKSPGxye7C?4 zIF+y<4vB0FvOS6Qwad>VEWiUO7O|b#zZ;j2WzLU#6+QPTa`wC6mI{Fsy+tHjB=Sz! zPRjkQi0{8nnPT_Js}l~T$E*1~qfKUxq<$n*4V`%=`_p8pQUjDmi%Ff?KLE6h_bP#E zCxG~)sY4uH5nt@IK21y)LV(^sXt2A$3 z2g=fSt=IOMBCVH-t;Zv&>Fmf6gd}E$-f$NKX`TS_j;fB$GgG))} z)To5oO=}34Un(aLJA%P$$64%)OERwp#Ghr8IKNoS$}Z_}b3z(Bf^(;jPTj2AvPKS! zIp$dG>(Bp};r_3K@qhl7OXv_c=%?__q6!1*f6%Dp#Zk;^#~KwJbKRjFgNp@nS!EZt z{j+lVN_0seo_Mu_Y)u`Bo?)C6IgV{f9b|XhH}kJvjS8PvH&^(QLwCyL^D3sl?1z6q1V76Z^_80~Fm-4RG$C zCMvp2V4I3gmP1U)M0dsmxGZKIikyYFMl$25Rmj9?GYO1~V-%coXtRs`wf%pcp}*b# z=+9J-S!+G8p46x{(-e2pIn|je7>ND{+dURiuZa@nqmYF|>U82@iQJW)WZ{AH`~D|7 zkV@+gAcubr%EA3eRWv>XElkHUz-$k;tkq_cVDdV3k;#FalA~Z&`rmk#|9Z4b(TYWH zD`eQjwB(m#gkCNTm#0f9zy(Yr?iWvpDEGH#n11EW6@`nWs5G|2k&QiyHfK#OPrvEo&!} z!{gb>D2Q$D>(sXh5QJ$R#&321+?W-$nOd#<=5k)w$W;=OLr%tfi~muo{L=g9G*F~w z@?1T$GHff2i3@#>1@{wy+Rlw3>`KE`?E_RvNpcfooYv_O0r@R(oo>Zg>~BHl{eYh* zBFVuOBNo|w8|cpEyHOm< z@34%HtpDd8{iB@(slr47!^_ZsLt4`*B`$WALsR80Qw#M;wuO=xXM0|nI?LJ*e)do_ zfo0Dja&D%_*W*E{_+U@t+Wuo+x)>18%H|o`xE3;7C3D>&H{9;tunsd0G$+}Ua#9nq zY7?ZJ_7m#K)`&5ME~?!jrK#?>?f5MfUqHNolRIjAS*d1AYoRW5YGVR=$+NTleSovJ zw+W1eMdF|fC>B1=LCU*IlsKdB3HtLeS?=?>0jC8hMj_ylbIp>qTF~THclIOamF^1j67uZpm%7 zdmzBs)63-W_dz&fl9rt1=Xqs#wL`1Ij?M%v$x@L4Hg_5`x_`lVjktV4@7AzF2UK4UDnHBom1*^A)pi@IppT#ulrH#+;46v*8q)WOYH5@bK+Vq zR_el1E|#VIQOLZg)FK3O3lV86TKBUyi?!~VYgA*FCMX2*nSV{W*U|M<^`epvffK}3 zyQX%n3fnPLtKob?60#ex!DW*hSoA`)5U4JD7eqZ0n*ArJ_L6bHyp+qm4F8=hwO4`f z0dFfwgv&bA<5>=Q>T?2liu#zq$T@!t;zIE(BU@C&8P{Jaj-SUe^T)j&J{dmJnD8Cg zDvjvj7ftF9O<*#+TgLLKrHa|m)tI}{On!;QCo$e6g$zbE+=0rYjXjyl z7g;!P2fQ9S4YSkYP>_+NjfiCXV$)M>Q3dB#em#xTk$PcC;)zfh$DX+#2uSL>m;k(k zodt(NU6*wT#DmJaSa()&<&Tl<3!B@&y;)#W>($?y@9nw}I&dmmtFv!f@hmL3A_VGC z173RiL@>xg<;6kFOoo9Wy&dKE%a9@{F5T|QHi&37l7?5suEPE3!&6})P2g+`_GCl( z7@HzgC$Mym``+mH4oFMym~UydaFoipfNQFxg8#J#v|;r<&gXuRltjl#bruK(cXE_oiL4}(g& z2eO0WKHv$i6-dJaoWQai7}nvCU}{n~7|y4r__(MD<6K4_?AFIGk6lmytK1{{0#!Ya zB)o3%VTaB++(Py;f+m7c<(&+2yj-|igdPhxbwZqlD>_9DqJIGQj=c!3U5!$3;)}GK zqU_MaVN+)lVA(`&IQ!wbW#N9FR4%5T6^IZFO(arm_Fv^5`~g2fI&W?=r11!*17Y~o zzyvgt66tN~SpM>garsLeHtAN;pOhHXYdstcWd!PwaE&r&ZFiG~@huTujJnhCk){r< z2iMt%W}dhHV)L~7`YV3o0=slklEbvjnen*3p0vIh`Q5&A2aNJc`Wg4z!3LUheRKt9)>d~8N@kS-MNwD8>$6HE!CAPH@zLT zfG${MexYI^cJche$=AyC>HbD~`3Ah8xm;{-o?g>q&Fp=I`yxuwiyEV@>+FgK&3-Ut z$wi1raAb7YapMTo=fMpiw4cX`(x)aj{OR^jz97vHyMR7t3t@PmO=a{;29)gBME2~Fx{&Za3&2%49KDw^O_l%Z$PW<+JhU7Thg69$)Wc3H}G zdbJu|YJ+X5S|fl02eaiiEV_S)!*BI3Z;K!IvDFCpNw%mwE;41$C%8_gg9xPvz{b?9 zkS}GnSu_Oh=jsRenfi&46^`Hc;s-#s1CNRD;EM!Y+31O=)G$1N zUq>6u3OkN89}b$dHk~NEi*&z< zH7Ks7h-#EmrWLq@FualdaJ5<^uuVvF?kgYyv>qu+aW?fNb*$fqJz{0kfBw#~n)l+J z@FKzKj#Qp3mBb=4iw|U_pyIN~GQtG1P^Ps~jG14mDZOF3S>{7<4l zdWH!3k?3EhgEB*zlsk?&EE<>S@K)|t*<~4(&Gl7KX~|$#2p|!x-Um14PZf8F6-4iF zOSw+P-=v~bE-(@UCF`#j8yyvz_BY{_D%U18X3g=vEfEztTgBXy1y&QbRiS|?>qgK} zmKz`feM`&C>wfFEU0D+U!C(9*cvLi?4QChC0!kevN)VBY2U2E`8)m_&;K8{Dh&6yv z1@S(epLsW6JH#OXWh2GtVFSBG#{$>&6VBNQBzn-Z<>rVU69@ zF{-e&a3numzEp{ilu7dj0?x9@&A7F>p#Vo49{m|3e%aBF`i_-Ab7A^n&;6tS#yfyX z*U$ma%|KOKrZT?i;D`Ku+i(f4xB*&d1vvNM&>4fn2VKDK@!>%@Qrc~^Q?@mMiCoOH!Ix8&|v9({*RB6;z9e=)vu1J{A7%2z?8z z5)QGLe2b_2MxQ^mXGmcAjG;@cN7uO&ejVutT;9Oqa}5iOI9aQ~rn3$@+gDu&<_AP1 zM!=Q=LsH%_32uKcCRGgD;^(#XCx^P9rkX@_lDSGe5%p0O*4et1U7QD;RnTPiBtk|K z0MHkyu*nUoFC9sSO-HW@fEDQESnGVkEdDIX{=uh*=M6tUc^CIZvw3`@HGzLB=cwk; zOo3Kq$#G8kVTt~o$=8CGb@jpP)y$kw0f`acxREiGA5y1k?;vvQ)1zCngJrKrJoh%d zw%j&tS2SI)a-@5WVI=9X>rU8rzZi~lhj;oh0CG|2dEWj;Jk*9U;sE4rjM3@ z!mS<;pX}Us>%WNsO~eB{?=raNM%a0on6t1^>X>;brB*Cl5;vSps~YfI+}rgN*6jio zE7o!DPSv+xpE>m?!9_x_n>Kr)zt~i-(Ci}8b5Xp30%&tK`z<^$=kj5a7(nAyMaCdcC(v-N^*~#p))Wg$+vF)SHg}Gvs~x+)n3^hm-7w1joLw z2DZ4vE%gSK2H=hH{GDW47CnMdIoATrMq^0 z@#?G+12d^IGp-R^kWPC&jSOi)F}vOpeK2NFXknCC-d&~E46@OUL=)iGO~@f=9=@$W4 z7|8l!h0h2@^A}e0wL);ujdQxK=9dQd@&tp9(!UhBOa1?2lmCszSmw=Yo@R;qLpEhB zLE^O2=gJ$E1Y#U0u$)mdMf(SR%Q40oO0dh}{FoL?Z+`Ec8igz_$)2`8p}Nm#emQ*% zw63KI&HDsSvB9v;d0bXVqq>(#UIfK4oskeOklfv-6XuzYm@xU1OZ=X$T69hjISD)* zp<8GFNWa-aUXZ+Tcc09MoR6vhIS^gq5VrvL_%wxU1*N597fs^*G*F z*00KDIA8W>?T(1s&xb8(mtQkKezlLUe;jH7wxiAwoqgWf9{4j#bPw#wQOd2=#kl9Z zbM~wGImz$;@$r5J>-QqrlgC&C9$Xyz9E1k~yU+Ylc27(ifcs_0mjwI@==W$+=Z5Yt z2glgF)F{%k7pX!#B<9Jk~IOG;Af{mQ3wX&#{ zZjA-go{3$;W4z&l{giP15^zMQR53Tw4ueAT(LCBsm?|pXozN$<;((a&NfvX>F4fpi z`f?Rh#X5p}b&|yB>L@zl!gxVKl^qA8iAbFSO0)T|;GNZ4S$_7)RuY!&Mp2N%S^$XkAmgB3sou*=0|6l*I!a_WY4OMXP}tNX1Ti z=y7HNAXjQ%*%e#WdvD1LnMZ{&S*$4u$afBWx4g??3 zc>(M0YQ|UUoJq2fk04mz6idkdSb2AfWgEIgPUYaORtZe4!; zJ3w+)G1Qt6XMu?vLI$(2af(`puX0}fI%@Ni5R?htnb;$}BM0$l(^rMHuum;UgG8?s z-Y8*>yoiQH0Nt7>R@Op6ptA`o(-1-*Co$$TWCkG@NMzlKU?92jsn?poh^V8D?xsQb>_No(+TzC?X%1#)Tax?0WLDpwAN9W)LRFNMUa$j833eTD zH?Ik8FEC8a@j-DpB-l&+8I;>B~Hmaq413#XvVlWT&WEzU^8kmLb`1_lXx3npLLD=z+O;m zROs)wIT6aaurylZqvB_0YP>k=XSw}?#$#_vPcRD-AZ| z*bBF5Q#*e7r&GFIvVa8Tg2!hUs`f~L&^iT{BMX#YYgtf2yDIuLNW?ziRMYr_kPmi4 z@z=GdGoqXhTj04t8^xndl)FXgGHyMbKE48G7^APK!URFCUMQqdthUCj@=MxULUeemayBYH&I>S7H<=*Koyl9#1G%3zHHEuVy$mGT3tEpeAl~D1e;LeF=Ax6+IYcm zw?krmJ@&FnGobL&DnT7TFm0!dxvT(Yr2>?;-Pnufk$27u0Ko`L?n2jzM>?~sqVOA7 z@$k!SS7Zj>3j{|;pS*n8&MK$*RM^c6s<(r~PPN8nX`PN~)hZDVp1;$l<V;uf8}`oGpzZq zzqpiBo78o29>C!1L;y#k5joO;1Z6pgJAjZ%Ew6NDCwZJ?^KAM2bmUGS+|(}0?Q`I; zk?t@Xi?7{K`5VT1as0Fi%2&#JdkF(790S(xKjV)~C7_eVR4Kthl^M3@qh=f*=zx~O zxwK1OUcy*hjPUjd3=FjF{_;u-Zeob#{e@nYxucf@@mqUPpQKWrgd9GHz8}&nFL@>X zLD5QA=swj3{!yMz6A4l2`9(?ntrxlXEP$4YPZ=4>u7pKJs;7!K$7^zjrAz&?PWM0_ z6rW27@2ICq~US2a>P_oY=AuRww9gceJ)ycc$5PICUn z6?&j4?$lamIq1Bio+9QtjVmBw>_K%8!eto?h6cDs_y*iK&6#>!u6m5R=tvbOtjV0CsrK5QtaaL;Vc@g~v8-aJf@q z*638Xu7;M&fx~@G+kIGv755bZ(AUvsF-kys1QWq$^UsfW5K8w&$50kk=Uk)77egkG zJVy7^emTvC^SdsK@>8kkcW99Y)iH+-0=tn#nQ2IVmjKG<9QY!E@HGSjxGrteVf{wu z0wsss7^Ha!v#1Tp#BJgF`FF63ryh3|Jp4Ddsy>zpjAclJdbG(fcLxo^y`Kd{P2mJTfx0mJa@iazAD%Y;=QmbT1$L*~QZo zkQFyo+pR2*OKK5WxN~=K5r)f694NCD<&HNqd509h-Wn3Jx~jyW^vEc^2-a#q^P>UqOmN;*lHl zC`MJB^$K(8%&T6xyA@AJ5e(a`s)D$B*sg4>iUPvFyLMx;?SX)>RCW_#}K9-?~s zIWet5x72aUd)Wmt-zW|gX!D+BfTJV_DEF1CAmU7L2DIz}gg5Al3zsaW ziDG2a{EPFV!zqdd;qnIUbU>y9glUu@m4SxFnJ*H((aqnoBB*BNSvCnP>qH-b8=%y- zND%{mOUt_Jh4l}@*wKzKjfEj8zMZ4h-1IE+ldBd_y&xJ`zt}m2+%InE_J3m773WS~ zJ?ISG6A&q0kI<;W@%v2`H>sy(Y~HE3gW26)VVE6$SF^UVK{uLsHC^opZLfoH@KXhT zmSuUwvl92w__s2yp-mNep@SeQD$=AA6qP1m1Ei}+lM;GpAtC}Q(wj&VrAU`v0z^PTKnOjQ zgn*QQA)zNg2%Ib6e%|q(Z+wqB&K~@5kD)hrS=TCatu?1!$8doROYYm#2$8jKE;1Sa z_{t@*^U0_Wnc=6JxP%`Q7WM9aHV*QZ2D%bI+#fu6m)Ihz(+C2(oy{dM7xK#8utII? z+~4C}0s$r3ktzc=0KvX-nnrBGlTont3x;+#?mAV2Ud<~62p^s`eHS`|grvD&`my{D zv0B#WE!aNvvLv52Ze!&z{rshqN?alNVX_^agf~tm@*<{jL}#u@jNa#+$TDq@+8Iz~ znOh*9jdq~Wz7;{L{^YKNrdHjL^s8d~O5~)j*Jmg_*&niJ=gvNq+SMJP{0wCP1l)RO zV1vwrJTbC85>q~SkJ0kEu^C!_C4S+!|UU1oIP%>v2xc zQ*- zflyR;$T+VSpZt;Vedik6G$lvE-XtAGk)ln>sJuL<`*m)S%!f37XTedC`7P0nrhfm$ zbkd4vl%(uzvW~GaA+jZ0(bwfRx1g-2N;zN}mEpS?%g@mb zA|_Q<$t8@ccxmlUsA7TcQsY&+Mv>xa?S$b_Qi8%^8&kr*!+omL!{ueBL^s&@x%t|U zqHVYh=jk^RHftZdgun!2`|rB2zSj<_<~)TxBH8REXWhDe10mn$LRxt4XlDM?@-Ba{ zwGi+_^Hbf{ApFX=VFQ*>O&Yi)*s4TC>{G}y+|bq0Rdsp+!uG5BWiDPRe9vbSZqJ`D zY}ej-*ov5QZJ#@)3YstBJXL0U8b0g~v5DSn$!v3c21tsm9Qd|O8Utq4%gY!stE6*z zX>SN*FeS3s24A~=>t>up2Xw@dT7yfT;Ro2hYB#G1J&;|sXyU>&YLQrWFcgg4*VYa` zUg;DpTrYF&P^2z_^~%=QCu9u3*6a_qL?QTe@xLKdIoH z45%V`?<&Tf@uW10t}KY8K5iOJ$UUufLMT}?ZW;st{ldrLGLY}FH+_96QR@vr1!Kn5 z=Q7cRCrYl&Woq>q_I7&CV{~}^I`e3Tc4EX55z{23D@_OaKY3h>h8y8gCC57t($$i0 zhJiMo`ks(QEm7PfNwVqM_4x3T0?aCq)0moTBnzvfJ0-dSRBuY$JX$^mt=UV^$o_6x zqIk-3-AKeThiQ406O(7;M!ESa0A#UoJh_)a0tJHFE7()Q*4Y6w|921$MjH{Cd=C^P z1oG^-x&kRB&E=)wYBk%L zE!vLJHLA+Y#$Szh=4;QK>B5Lh53Ocqq!lgkaDAV<8mm=kUbGa;K?FkD_oROt?;h<1 z<%ikl;px+V^owoNQVX`nd9KlB9IfZA>Q7LKOX{Z9*woCT zJm`#xTlwJ0PwW?x+&h^Zs4lG6pAgY2w3<#0ALDA-&T_XH0A7Pt+>%B}qP*Hq`PrvO zFB%Cn%W^H-;1#X$l(V$52%C-G_t(vT7U!2(^Vs;%uX|Fxn_NyMW}x2)?%K~**eDSM z5I@O8IRAy4wTaJnBF&hBg*8$JNOp-kG=u~z?*KJ5@~4?_NxbT3%?eU`c}3HwBB950 zui&wX`K7eS3z*hVwj0GrHcIe>#>rmDDIMB|?H;7-90Fk%G2C=$WvAti&>ItmubPcT zyhmQ!bFP$g_4RH;%Ty;LlaikVh9vG^$G0Dx&zs6O*hZ2}x?Aw`cqZmLQ@8D?D=7}* z6Peh%u2*;Uz44K*C3arCY+khyi^B0KPek<%=0~yiGTr;gDxMJ=FfH=^i^v-82jlA> zc5jFqTv?Hm(+=tFonC6B&NLS+;p6h0-4HS0Z#|TgiQIsWRgp~2;-0**l2)i3?OeHA zING8&hONbah}Vh|1v@pxHDe!GLBCCXbRuO#E}PE@OqSTeCpUPkOj+&XMHe=XBw0Ey zSYVAT8>$n#o><8tzbiJ=@k^jkhoYt`@fa~mBJRzI%{HR?!ZqU4KdF6wb z`QUSwUbu-_7!K>|zmgYIelolxZtc=j<1B`%4B+ec1 zh7MT4iL{k+IMGlz)@GNpvw%gKBb)_IkTa*h{e;eeI?T|B+Sc743%YY$QFM{;2_Jcs zj1RDiE=vRaZjlpnu^$7uKa$p-h=U3e_FDY6))Am&y939t4sM2Z;A1_3qU>7;`GCUw zEd?-1@wzKi`-9PnocZ^@Ijye_^phJCn(<>anIh6Zqo;b&i7inVh_>*lJ8yJ8<@j?d zIDI(@jJfr}Y-#R1Xrl&-aG&%@a~UGwSkJvLc>z-J?*)MqH5m-przX zLCOFn`U!R361Ro766?(r?FD^|2V`i~7@96o- z4XWQDG=WR7hi3;q1wQ^3(bc~)`xETgt7t*eZ?tojq;^LtCz$Kyyc$Ndaq$sYW+E7c z7FchHwB0r1FtM@jk-%vn(=(MFwE9MQW@d5#n&Yr^t|=ko4c1$4N0a_MxA=t&YF;Eh zgHpIwm+j+K<70dRgLzZkhr5$b3T$)c)R)s(@1ONElCIG%Jf3^TpXe5mv&$YffnkN7 zf4IR_Mza=w93h9iS$aN5dSPR?NDQKEP2hO0siA98^l@M`^NVfa6wm^XMp8;$X~s0$AD({B1Di zgy*g?0lhcc;lte21CBy&R%>lYa^d=RNbO2DmyCB`-kH3-iFWldQycyf>}vS+9SH^E8QLwUn12q^!MN);?5_QZ z6*|H(E!l8SIk?><^v6XfVlL&)1oKbq@wo*HuS?Nuhk!(e6Bzj#3=bP#t{qQqUjLeW zjh1I2Vxr$HFWwoqKdD=19aI)wm>WGl5Sfu5h_VY2IC0ZZiJ9EdlL-_y5JvFC+*@-! z8YyjVQ#+s$&F~4?o}8tc#q~$7_xbQybVQ8HHeZSp4$(EK>om1C^`a$LlC4r3b}mtc z>2ijejWMdd!eqw+yC;hr4f{Vq7QoCUt*Z^|q75U6TWbvfi-! zt|Oy)f1aT~)+}8+cg<^m--*ov(D%z>Rhe4HyVtR*<+LQbE=*D8CHsN&{D+RzdP7F% zdZt7C2gWAOOEt^cyKYpCi(M}W7Jw3=>gJDplBa#YTWCqWm;q%sHl4K4j zc;1IN^~K?Gn&HH1ZbMFQzET&W))nD=8``h{Q3O!-5I_6x8m(b_w-+$Gc;$)H-`F?p z{Mzc-6*?trx-crF6(iUDTyLY7-_F(M`dU>V#&u|-P#=OyL^%|QSHsZU)>Idpj2ZiX zq17czwoyC)7m*m4MHy@cjIES+gA?MIgfLS zk3vml5$Zl(LRDPmDUC{$_iU**k)myrN&QfLHq3Epc-J=&@Q!9338(cbj7rKqPprN# znB57x_RTZjl@94J%C99My%2h}5rQ_I<+jadDCGJTx z(_+^P$B?buz3K#xA?P`fn^TM|^e%|pC!Vx4+A+`m9s|C%yxrCNR+@i#+~G!19^4F{bs zR$^4X&Q1Pjuvi!DxPfN-2ER2oKnCw|f{968&Y6B<&;6IDd*7EoZ_HP*GBTy+$Uweb z@R;x0DU_?UY{ty*Jm=Qv$ z5Y7wH+4o8U7cFO~n2!@lAEOLe=1UgtLct=#6usyqDZBA(HD|dB5iqvjO?AIWlQd%ga<4$FDcYPeOyC!jZ z8IHt3=e|UC($ikNEB9)v;Ek35dL|nQ7{2d01%SKS!jWU!T@Ol&lUUBXp0eS_`1-dz|K6-21Sv~=PFE8*uUwkb4wr1&lckWLGXjCRI*{6am3NsXb z6<>evIK~5Rg?|ki+%f=s_1SO0({w>PFaPZ2kvSq8JBCKHo)e>)vX@kQVYEQ;WNp_l zRi0GqNKdcOg-9X#VR+C6FWYVRJ1dzj``1d=H?5?GzDE@V9~tG6EWFvq&7qSo%to7Z zbH`Wg4{b9x^_@;{HB=2XsZO+<>@6v>9V+f1gAvdD6X-}jD2r2X1;NjrbZgwzQ2tUE&#RlHJ%}WH~;v>Pm8ZvUciX?OeQl zwXklAqw6bD&ALP|u!yk`wnFF1nMZzc3rAy-NgLP-T<#Is<_bU5ApIPipt1VBpF|lk zt%*SSaFTL@GDkMU7YtoNZ8c2GJnAJNZ^aoq>|PL{APDWTpy(RddC|QNZR8-S)Lu8! znUm?%s)^pfO2chbp=p3ita{SP3f?t0viYlw3*?~7kRmd%7`{?>Ql@6fQl`)%KG`>D1=QqrH)Q3hbB+0SL5n-L+R6WAo?HK530^3ve=PiRFi-i-@) zd!fcaY%GzQkm&>WY-wg2VelV0~rt? zDL<|M(zENM?&0|ve2u+$XF~pPalQ=ow4!nk*`J%FY9hqi+z>ueYaeQEf+Q0~l4MSE z+>ltREPbbQ8#%iJ-M_%d=}qzFXnPt_^2sOUH$R*Jbi6@pmm@m9gU}JBJ-7w%E0rQn9BSe5~Y$f=);U zTlj*sctHsa8@&cixr62F(D%Ba0|{1x0%}_8xsJig0HY0T5Ub^4)@GzSIc}3)fASZU z{R8Z*V+|RjJ1HqCfZQ%`#@=Pb5e-ww1R;BE_4pHdPziS+D_4ZjTd9IU$CY}n?GX3b zr^U91)=4^0ubsOsV*E8?WV(n5Lm`Dq)bOGilQ%$3KLFIULm%~TW&vdvC7Ja)ZlUx4 zt^57s)WJceGJ-5fTx2wFVRNMDZ}Ju-lch`*<78-ZmTI6R>rA`5Cu1hJRhd+S`o=k-GO4s<%l6gE>s&*SG*XXBVjalB);gN* zYI%am6!yb&|C|>6fqN)M80^==izf_GGIc>`-(w1(RF;}uH<-tl=SH<4C6b4QhZFD9 zR_9@R;*_PVB^)f}5d~TRr4KD#0L#3%nKA&z$qm{^6>s=qArO&!OPsrKPXp-q_u~(d; zKJxcNb9oK;AU3{(OV<2c@rvGeu4-|9YBtV@_S%!{GQn~_?#!MTO~+O84W&}v3gDsI z+J~Tl-yrc5{cvb<+Vt0RYH_89zOusLJ=%HC{iVI!ZH(j>LZ5SPvE#dQS(zlJ>*h3C!-;WZo)>aMwM7aa1VYzLFlyQ75OiOgP?U zsTytwElIw;V`b?H-crwnTIC-PtFfEddhwr;A-mLBB- zPl@kJ7WayI?LNbm!4vC9rup?EmIvC{83N?!Q=8;3GM z!B41h_IV?6rNUXR9t|T47?#<(t#(M(tWOv=nZA1}zVuCbX*zj-Yz^IQq%{btoiz$QLWGcg%{s`LGnS=&-(LHPY`GhYTU7_Ts}iTTvFB6rr|%ki_N zukD1&%1&NnKi4}7iuZ3LJza3zqbM0uklQ+wgMV;vqMfN`cdBkLAD4GQ1)t)HZI2AZ z8V=eqo9&rk+90g_jBe?D>ql|#CMG*f$V+9m!{6?lxcG=Q8B%E2trhykC-|h8W$d$( zL5-TML)V!>eV@Z=u;Nc&)1lD?ro_xk28CdG5s6)^eRgPwQ%seLJ>to8@hW)pekTk3 zY2?fW>7*vH?B&h_gtvCd@4FF?i-Xz%20vt&L)TI0pGG-YH)t4nIpdSf88hCZ}jB4zPjqmgo$v#Qkvu=3brQ>{~>&xvKVONJw4zu|I2 zzstV^jFt^~OMS_Gc?)icebqzG_{-XmQ)pL@p_44k`@%y=gE!9cPTVvI)}ygT1@d@w zpPWBniu4;;bzVCidN~RV2FVahZdYB~7dO8W>&APVmIN7f-P7o5g0LxBr#zvCgZYsa zN}xv@I$l!h7NO?nBc`sn66GNe?c#YLs0*~50d<`;-?x3E4b9!?_aO7m)1D%HVR9Gh zt*NOx0`N(+@p{EeEjFS!V(oN)kcxN3NEwX1Ic*{1|1utG&U-p^_EA5mb3l@URHYNr zl!A5LXahaMg%honPNZf*xl1#7b#Tua*%kT%_w78iD-C|9()`-^20f^urIsyuka&2N zy(?8i4vB9er&dR0~?l%o=WHjN7Wb4#2utkn)rtuhK*&Q z;o&ymJP|6PE4`X$uRE*oF-+m8{jEl^a$9Qku142Fr{szVJ`F7|+?0aN7&Wtcx|G0U zP+^ixz>XNtbLE`P0!%g!)msf-W#l9xi{l^A29(^=h75=(7 zh&R|}#7d7v)zEjV`UQmc!Rcnb-%pCo(?_&SQ960}i!h$m*tIM9OR*Q?>rv5p8&?NF z%`*M2ay{syjQob#(~!o?vveGGSA_7HZ?=N^B>?x zTjzg(pX2c>V6$JX_Sr~^nsGkg^sWU$({d=JP#M2k^dqY$ORbEokbu{+0}~>*6la&N z+}TKZ!8F1cxIjZNP5VL#t+CptZ?4{RnmE7;IFk+9Z|(<+bmCwis%?q03U)+@0E&$XRJ z!S&i@pLQ(K$@HOV{&D>ypz7xT%As=M=EsNlVKrm8F+1s30%S1nJ+OE(Rh52R0IzT;Iqd_gH5`I)NB5&KmYEN7OYfB1t5}kJ&F_v4 z7dFM9bu4pkku1}~bl7m5mdms@DWvNvwxX!l^oO>_`r5AP+472O8~nJw)#wKRorHsEhb*?m zH7GnTbFHpX0pg1<#4={gkr@UpAPYHUeX-Pc=BMP7=1)|9AG+Y(~?UxSbT*F@ws(12?vRkSUha(h){{_KZE^^^)q*8T(g#QJ=8 z+~I=`F;I?iaYFu-(5wGy< zQJ)%=bBZ=W50~qEHLO-F=@30|Db9&qUf%CLj&%GabZ{;B7zjw=T<)fxHAN}Nb=Dmn zQrohgAi1+ZWV6J;uQ{oa4Bg#?GCc0d(%463OQ1*+hNSrbM*-;F`rDg=hFh7m;S=}O zwUNXKL4-9jt6cn{WGR9YVU_0UJh=p`zN8~&fi?>ObLqPDwy12SRJg0Jt^lTaY+|<2 z<~&7J|e$O?>ZlFs&&y8}wX%wl39>N6`ZCiD`@G zA-$OyFmE9qbD!C?*73sp$9^ub%d{ai1g0j>jezZi9GL&sLj1wmAf;y35Tfa6T;RHY z8|Of;hd7{n0D}+qQELeMfhzyS$(}(%!%xU3Q&asZ!TY$fjeo3{RZk^*y-}mPxo_yv zzU61@#q$xSPcBoIcp&L;x@Iag3US%I5U3ut4YfG)5cYXX?7mMe_U^yKBmCy~^h0%k zsAV#QBn!<0F>bJiv7XzM4Ce!Rl9%C+*ESE!V!dk4aA5Lb4cOoVC$~Rtn1%|%$nm)P z+XG;zGKylC93lorxS^~pF@QtkhQ8Tu3FYB+%>fH6oZN#9m9QW9uJD-NJMCLINl4}o zPHPC+=MGJtf0oc+)+8b#>eaz?CmnafE8o;4n;ffPR-|?Z&~Pf+L+N>A=h@~ei(gR& z%6>NYv>ejOGjm2&Ht0DFU=qAGp6_gQm62#c8%By?tMwdMRt$xUcoIBz{>K9Af{mLFf~mT5v+2>8VwyWo*leP$mVh=RzY? zs3LysCrptR!BvvW%rc0JKL(S?a1(Nu9=8qWTFat4Z0Ql|q>lGE{HTwG+_0bt@{(;K4MN zRO>dm+E?)5_5^MR5I~D83#bu>>3lqx_Nanfg97fHMIH*Fd$ag$JJLlYF@cI1+sZ656E>+L_d*h zOkarH>=re%XvGzq+CYlsm%$nXW21bSUhicaNr!4ghr95n>MsXmEE&!B>f;~#Z_MjK z1+BYO)?4=)_^x47xC*I*WrB-ie1r-Gj+Ly-MT$t0fO1SicsiI))X`j@0ogrB&`z~l z3tdi5D?TZv7QRPTEvZ@aJvN!_=-m?93lWZl4<+X<5Jmtr7v0c$Duu0a$w!m>TLLbh zuToT8+X1WU@N0mr4FJG^^lU}>NXCAqW9J3Z)MLTe2Ax70G)I$-9V<Nh2$upsjY2;w2b+GeU}E(94MPEy|0E{)O-K91h??8h*+ znl?T?5#y-#yMej9YjgKaliU5Rb41E^Vxt;8nLV!84-i7vM|RGcm6Yjl$(d2AdY4vm zpv|d~8Zo{#0ok!v&0IZh1{JTmrzflk@~W6=Vt~sI zprpBCxvaUeT3_b`XuR{+e%FL~`kDE{zaYjw^sg>VWt221wYc$^`QEUHce35M2=idh zv?&B%CP(K;x&6`2>+C(6KC0>-LPvz!(kf7QdR!LH-_JDkaK=_|nR_*iZF^13Ui6ok zy#h%}E&#aY4^{jqo#alQ-tt6Bhk+vkP5YfZ;z`?oH>G$`>2t>CkD-m~{!G!{d5+o* zQp;eTGIz4tcq3)^dz~(g=yMl>*`*whaYKBJ-d=t(xoFM%Zu=EjcSH-HrQk47U+)kPDHPCY0;Ty$ZK1{2+i`}Fb{Aar(;LZbdb8S5A7k@YP2X1z@=xIR1pzHKO z;w*rnXP{3Z=R!pE3!1aCI1Y}hnUJ~+b#w1?nr@KN%$kLQaGE znGfGZpDH;q)5J~Hc8i_RS|O)^9f=6+n?w|{>mnD(pyVw2CGKUYPYiI)(0h`hsr}23<8G!nE%UeXP6~OZITizhA>~b2?)tuM0=bsX@h-u;5XzJfFI6hg~ zubVa(#&Pd?Y$7{jQ4X*IF~&-Ej7~g!_G183zleM22s6aIaVVD|zLEL=)N_nsH-MQvn@EG$(Coavp7 zcZVBe41W(t{)=GyUIQeYNoR6ww?s9SoB($(Sy-Bzo&6jiA0Op|WxifyJx7DW|CO8n z*Z+Na`T<}E^YsPH$)}s%h<>2&-McsJU(MiQdk}`^>7vBnZ;!ht9yZsgEff(U}x#)O*9 zUu1u9^F2YsLS;q%f~Ju%Z^3=)cHJ>6$u192(*C%vF_uq9v;fYov~Rg(>S;`DY2GVN zcM2W}0#PuBx}@jXzn%+_)q)RY1vzox&*>3AGoJx|=Ma9e!kGY69d-bYt_8*9@n9*F z*o=YVxr%^aGwweaLK{FP1@L2ELyh$mVX_?w<4nIzmj34)@$TAZRE=0IY$za5R|SfmBvE+gK(FY+B{TcoiCy&xAL5-u%etnid@ZL(iOCa`87rZ>y@T2pecPw4=8}U5A zEw5@&_GElX|CwKz2Jv!k;u1(v!{5E*%_qifU6hxymE){;)v5par%=U#e??zg4>>7* zL-E8)O0`YUM5}q0rz9n`P+5QGFu#aD6l04=Hbg&k8u3?lC=^Oh;L+q)U2=Pk~tuzpyUxT zxseGKP7TWk2yIExKP7DjbQ&eUSG!F8P{rgJBXvy>bY?jhF6+9Ule9UNJ18GGM)jIA z*a_{d&^RvR_~Y7_ciBttTyZKq!7ne%`>b}K^(ZtK&PN+JFomKdmo?9t)+E?8zL;19 z-0s_U?7nfpy?B*6BZMxsA&9DPiY9%AY<@r*A@a+`-zG)>fiS}6!D<0*PNBPbU}>Fa zD9^AgS!E3_e86hAL+`916vn=q*5K@(a3 zZHd*$YxCB(*U8yB8e$fE!;+8Fr>EuIXL+r0S$NtUA8VSYguQr0^vGqJlC*Y;#1!ZVUR8iqtGdEJbXe**o@&KwWt{z}() zc&6^|8QK$`j^T2dnuN=mCSD|0A+$ATq{5>w4j!o^ry>;L zvPi_lqP6h7_42!X@TrKecgh`>hcJ1@kwev{;ZVZ&0?cJ=Y#$JR(zP;TWsYX$5v^ki z)GnY=4IeH7ZtwxSg}}_3yoXC7sO3A&20cx!hd8T@xr->>AFMjw&yPOa)dr7~+-fTy zKDX(8u0oWJ{wDBV-+b;S88GQ;Qf*QSOSt5d(4A{2bp>hT?$z_NXTF~5JPyiBF?;O& zeFqM_E+o{4%0JTM7BSmZgxWx~51!Auv&0Qd^f_0X$>PFQ@#zG~sPziA^ZB=n0^}dy zpg^jZR3T~8t4h4A-B0plmFER%)yvC$xDw*Rd{^Bp+_E*nTIVXNrN#mrp`t$M^bZTU8cdN_r;>))7UvABVg!*z-`=*c!0tI z-IrZ=3a{<6Y|}Gqe^)!d(w#|(@Q2U^z+X{t*()iHGT|@|A<~VmDhiS@lZ2>Ks z=>}y+CQ3m~^b6g?AAf(-L0;G-Qs3M@1r@-(WXYu8H_-m$*CUYNks&q8rsy|(^wp&@ zSc1GqM0e$kB81Fc(=!ex$7e^Bdq+QU7PbK%m1r=KFM}>}Z8_EyAz0J}+w=UOGOJ;lS5iO`_0aG1he_X!FPUD34oo|1fKkfK$c$9$q6w`Y zN9xxS8$5JZ1y95tB1AyH$ih)uEjo+(4RF=*2Y>DEpq~bEo<+6{H~M3)F|o$l>c!f+ z!3Y|Thf4|?SGrid+U$)zH3d!%>{AiA5MT7*m9DTLN9%Y5dz+zunaj@j3t{ zi@KmUR8fB?O+&3kC^>50yhUxK&R@_nFM)!6cg1gfA@eTD4V1e@wZIY|120$T(PFXU z+RCnf3E>-8h_=D3pR4e58U%VZ!~vb={m0LpPxg@&?39HjzCO8wL5`0e=ygeEF5Ut4v>uHU^i%0NwlwNDD#D5 zQ_EOqtzC=bEwSpg+jor??y}iTqZ#QpsJk0j_wE04vm4&cP#7Rcc@lf=I#aFhT4Hz* zM!bR=Q4Xs~%(>@=zB2{Q3{501c*V1B5fC3zKz&U!R#;|7fNoA4!FCReiLFv)dX=oNW`@dWO6jGv$ zoz)3@Kr10;HgVGu3}bCfW%ze4t<0)V0CyQJlJ0X6cv9EiC-q!f2twk+&MJLzul4p6 zWhc{I8mY76#S=qHJLB(3!b~IG4^Q3pHONQR1OZ8Od;qGSlfH%}Use zeOW6HBLg7!+9X4uH44RjS#NxZx!1o-{5sE@*bq6E^>FaB#ro=3(F9TE;)T!!h!k0jb;^Ea=&&(6Ehu9MtQHDDWpvx$o zY`vE0{-Q)8CXcS;v}fFHwGtK>!j)LF`*ZfaOn%^zBQ@it7)+#NWlKbo2Xkz`LB-WV zb7T{F&mL%OBfFGa9;rJs66>t3Q!zea_?RBB1o{)#DGS8yx!>}l`?q*B)-G(NbdbB# ze?dBOmeFtKuXbkVMx;SaGv1fxXH?`{Xs!&?4@I1^Y&+9ZlO(FVxrsAr=d3v4%Mk+r-3JnMd{^oR*8wzLx{ zfSO=lz;+&pS{GWh8pO>{9n}Z2)Tyn-S79YyS!zq{J!#{S7X%pv%>*uSTv)enWDbZ9 zRt8?*F{&JRAX>l3vT3a8?z9Z(53<+;O(4b9$q%Y*ZLK$Sa$k+nX-kaE`I7SOw*3<# zVFgDTTHLZ4iwO+pkdc>xC#v!9mkFM>f+J>Ybp?Hi8SeIP2K;{EQ4 zQy0=V)9i!Vn-Ik4O2>%UUlfRrz?Vlh83KAZK^dpT7YPv9l|oCD+B^ZxLy(gLS>-f+ zl=YBsm`T+&$0n{QIW4aQMOSqeE=7JJ?Zl`$euPxFPcG10DHsk7$RmwHJ#Rv`N4 zsU&aMwe0}z;kWw5gW{VC%0A%Gry#EKr~dph0*p7;v;ZH;+wy|5ep`9%@`-|7e}0uz zCUBL^A9iEcw|vFH#LcU8z%Tydzy2S5kK2{!^m<*Q*&>g13TMVIH2^03iRtO?hMh-Oyxg|xw%d+`IjU%IVcmBfGd%(ZCyENDmwE0No z1)EtXs$gT^aL?~HcmL;Um7W9Hs=fOSw!BFyqv-*z+OP69_Ajd4Iq+_|p|TgZI7WCY zKqd={5iH~bmgmncP9PfGNovG|$ClZuI&I!c}NOvO_8+zfMfYrnPXlHa_F~^AA+c2fheC3@RtW&d4)|8^A@i z9M&3^Qy0!{na_U;vHy~<@LGWPAj(m&)!6Ypx>c4l(I=e-|2zT=!@xy+gv^zc;Fiwb1aXwftG?WL>t-Hp z!JN~O3pu~%sQLFr`F4XCF7Jv$RRQiHpuWXnu!$gZtn&P0BLC+$R}Ky$@{-lBP1nlJ z1X9~L*~;WEF!V2`;Ca?a<(t$qKnD!K%>cC(+j!yR^#^a`*Ix;H%m`es-wJI_cJO9y z9%k*IXwJXkMQdWDC{A$8a*?f9d-(k3y*z_lv;D7b?SI=q<~4AYa~`KWPHx@YHE^WB zXa1S-?9kR_|NRL6trjmAWf=dP8M*u|;}6&}{($9o3;cg!{0s>?Gq7^E^^5PMZw8^W z3z03xf9~R*Zh%{%;iI^I`Rvo1-c~3wl)vmR_;@h@(OY&!>@WZI)Q17i&RES^wJi|& zbAwBj00;3biEGqn>tQ-=5-51#p%3)^{(=Z+;5<$D6JaI);Yi zbN%^Mu;4x5*o%L?6`jqul99zKA@T<_Z2!jm!@S@aZgV&K)f$`MYW;d3u2#tXxvi+m zg8NsqY!%;v#KXR9eV{p&kc_Qp{qJx4Z-(dlSQy+&IQ}i@_QpvelFpkHUt1s8DB}dg z^slg<+4lgeOmnP=5Sy?rg9K7&-uROO4);=Vzw07x>}Al=zm>3i0;R-^V++kSHf+B$zQU2=|wVlNy`DbP<0?r@xFQjR9g(Z!fB+=BNI@L zcW3LND}C~FHtZ7DD&2AuXfrurpTHnX52g2{_v0ZFH-lz<7v$n6f+uB?548HDmpFP` zDqT@cy`Y?gCRKrIme`tfN~PHA%@yX^3#O$;KnqroMx7}O)EIJ$j}kV#!RRB5RYJumHNTKbDn2hK28$jxgPpZtW zGrrSdklkq4>sbv&*VmxOg&&VMM^oS?2asp3GaV0SViQ)a59EmfTz_alGMCWFR1vkL5FU4m(bonSU;Y`Loq8NU~asg@UR*WUOkeDczR2QJ!Xp-VG( z?Ib%)8&UoUcxg|eo0bkoTz&Hw+GZRoo2?p~Tux&^r0dnlz=EI8jnWHg@2>}qRkRr= zRIBUvd<|j~KD}{Yr&!9iZ}K&4X2{rNdZ=~E2C!VB3G>q#Aw@83iWNzA&b^G)t!&7_5Dt8cx}7xB*^`ySy?!!I-lMQeLEt@q zbq&N|$#`bTlBginNX5g2%$uD7oeJ}x;Ir#d_!caA>R8ZWm17&2L&NP86PJVZYF%0zja zt(V{2EmHI8+(Xoq(UB^g8x zarwr`)74%metcV;8CuF3dw;cNK*h;0#Zf`xo}%}cz2Mnj;ZOj%jgzo`kGqVpWgWU0 zoJ0IR*YQSlzFnF1ZQf49h1|hnx9yV<5hE*qVvy{DVVQm8Y29~5p$wt(3x=gG6E~G} zO{P1Pk9{|Nz^Z_qedsZ0-~|{YN$Y@R@G-XB5je$Jrg8S&$ubK>RXz{x>=zvzfza{AFtw5ra*n`*XzvuYSIa@Bu4kf)|hI9o3MJ~MNzP0s>&rTz8! zi$3~EUSt`|mxJorZJ}k)CX#e3-G6mK2_JHo%>8_K?2-0*)9@Wy6V8R22?MvIo@+p5 zxTe;l47`{(W;6F{oc~bwH?Z3hBT6qH(=Iq?fs%auqIwi`n_@uM>9iHKK9E(TmG-3H z>s}4bJ`T)a6!SeczBHW(8;{ghEWlJM6M;$V=9w%yyGvtDCRfdRr}cHsr)S%jghm@R zxP%vNSzsqtKXiN;Pua~Hm#GT&rDwu{fQl-BqaUwhE870kD_RGnC=tM$$T*V&B$l!wTNJ2W$c(;H|AUNot6f4nvz8jsf3rB4&K8y`7J=1X_{X1 ziL9qQ-@(-g=9sD2BPL6@g_6qL;wP25+M(oSLbBN%(hxf(O4rwPtN$umnYKxmzi+d; zk>AF{bMY@c@I5eEfDMHbFya=fC?7hBt6Jx4NK2Qs4v~dRZXkAXdM-kKV{O4i#I)4A zq8N&=O+rk4C3k{uI+DZ^0=bW>@m`x#=^)P$^KDXF?~dry2C^cw-%8j|=QszZ5Y}h? zus&YamTkC8I<>-gO+Kl1kOY>Xb>Cq>7*yz=a1*`BE@k})hGVP8u?%J9 zZG=`AJuMpOt6Q$calKOZjXB1B>&#bkM;Pqv(F3VE5|dpQp=(H{iQ4~ z?sIl_wv$47>DMgyU|*xhlA8nusjV-21ByNf)j7H*Al7_h_;0+P%#pd%l0+MYy*kME z?0k0NK&h*6ntF7nHOF=OtB#&*J#$6zY}5RUU`OuNoeZsU7r&=n4Po21^Zv6veJ3wx zpaa(B5$~a$O8@-j&+rj}e@4pvkHcvcuI^%e@&5O>y}82dW6m3^8zbE!bs9rCR2Uu8 zf$zcbQXd~&iI1HfD0y-_S+Uw&Uq{-c;*L4H*yAv3jzAWkbZ};xDP`gJ;Z} zkEYvoXBh_qIx*E7Bjpn>vBeRQU#|Kyb#_y^yLDIqbL~5C54)tw#CwvOlxxy?!Cldo zZn<;zxXP{{f*Ow(%iv%W|NR%S>9fKYL9*tUWsv(eCu98*>e((Prb8b4nv)?sV0KNb z^~MRtQ&;i+Ga~(b)a2&zqr0{p{O2!!_V$3+ST;MKqzJ!3eUtmDY{$UYoc<#Jf2#6x z6cOw4&C%i+UFk^>uN(U7v6>S_We&#>MJvw$(@Eu`)A^yb$LOTsVi2!bT{^#D>s#rSlb6=I$Y3$6g(qn6yD>M|-x|Uyo%HGBc z&%rc|6>?QFVc^nG0{P+o9GvgOcRE)E&(lQ--R&nr`PBBVz_i zZA9vK>|V_Hd3q?bWCW-K#x(D*l{V~3*UYquZ4;Z`UpIHv|COq*%d4@oFBAg29)A= zz)P4DGq6OZW<+LabvTard(%QlE{l_*7lLZmSA`7HqBdJX!AoSp^ruF{L8QW zkMHNhPaWhx?er^Ed_H{}i6=#C{ z;jWgt8N@x7R{sY`XQ4rlEOV^C!l4jMreQ2)2ErRJ>oWTfI9F)vB9Hlr4{|W_w>6F9 zBDdaLdiYrSU7e$uija0fYWfc&9(i}(A~({&m1IHM_xlGN8FuZU{7;?Z-bUQbLy!Ib z(kh-l^=Fv*L5qVDyGF{t-qP_Eld5(c-L0N{F|o_Uq}ua(wEz9JwTgwp?@##t0r5cO z@iHE+zu(L^=bUfendc9kVc^+&uf6VTUDvhNDp)oJ7VuOX zqv5VX2DlDjViA1|ixjbp%W4BB59Z)|2k`=2oD^SUH(XIwpBDB~c4IP|(q59wZsk>* zKcQ;>I8=rCY;tfs=dkKxQ=L}R*OS}x-ZEO50^8;#n(dLtuQ>~ zw%r-v?=F60K2;j6a;JrU6-lEQAry?0wC+qzu)ghfv?2ob5C`G4^VQr*KSJoCdrcjWSCVj< z(q$(KwRgLo60!zTNX9)M2HoC5U>C>GF^H!-;0%!lqrX?9qB?*BhhvTXD)p&lu}o0#*uRkp`lsp*gT+>v(kbnw&izEuti~(vsa4AZA?~+V zw$yT!zJZan@;};x%i)Vpp~xDES|>v*^%1dpGUDc4QUq7Ox9zt1M}E_BlZ@gv!CL)! z9Iw$XyXY`qe`}bY**#V+VPZU-9yA97d*L$WTUF#X>l9sYwyglrOqx3GyNBD@$o&Ye zgOtFYSL_4Se|Zr71)WO+%6AXqv$gdOYm!ciYkct%reWU}!?2Q^7_$=n)*XP9FHff10CNfE1NQOm0xZNoP zCjugAzdO#fcVTu@@qhJX?BlH`Ay~KCnJ5YcEKnD}5p6`@%i1j9L-q(xV3!1*+>{pK z;7%^A$o<)pSe;hi%X;Q#&V&W?iKJi$^oMl4TTKPy4+T7FlJPNpaolggMl9Kikpepf^;iD&w`Zz#Gx@VMX@A9|SH>$empJUr%uC}u zEdS5v-|d&};yTZsp7JD-JRE9B)nQ?SvrlC6V9G}6_eZZCEfWbgA(p>7{f6H}}? zj27(Sa<#4kEA2j{b;)l6SEv5yN0nNKL&L6Ak+2sgSU!wQ`hBTxw^!3?C6pIgvgs3P zbjsCX)poo3k@|rnSaSo{a0MGe$8zj_r`{nrq+V<7tRw6R{iRv5n>huHIX~eZa^7d3 z-$!Fh)>oZ}8}MI!v>t&drTdKIDZ$51d<`wja;8-uz=U>jaLEwxPQt1`3U2(SVj(T7VDE%bPa+v5eBXYtb~ zOP6B6W=!z{;1(lgMO-lVJQcVe2XZ0{s9ZeZPLzvNCAq_Ej44x(_vY%}+MCa(xog~u zf%PCsAHNEh8cK5$jUBHuY!oz@iy~VswWqE8B9Z`%0GVjTWkH^SG{k^;GkK)JYT9SfA@2!IJU$;Ze~W-k&&BO@@mV2Xi70F$uH5WNKdo|!O!>}yCy$-!yy&=^OL+e#-*0gKFOkVF z-iW^F5J7vNQjN5YPVCXL))OOA;)QM~)iZw|PT9sY#fTd0UK)w~81@g!vH$&WX9+GH zj_vnm8CrS!8YX@)*h?nxay00)z1+FBW(P+rpw|00qZ;~tge!gPu~T*VvN|Ht!H~|L zw?pBbgW2D z#t-nMN7GdzOC}@T+5Mib9?e?KrPrWg!uN6}$?u&Xnk=wL5)5&RR64IC$FsZjgeDV( zXDQTCl$nofMBLu);2O`ReiuIdK=l#6)gLpU9#iv`&T^HVuqnyum_xpPXOxJ-biHkk z;_>Ws{f9XwDBEqeRQ>siZ(f&TvUw`Z$GJMMBD&%(X<}aK#S9ZAa5)O-iC%E@3d6ub zzxc8clc12>l+XSy5a)aO?i5V`7^>YMm&&K1*ycgoj{s>A4oZ)!st?yydu2f%MEr*H z!?GUh4N+%*LF*Etn>%6$40c~a3RojH9NH1`c$Age-35Ay@;x8u!U=oKOEaf?{|uR4 z>t7xs2`VZS0Jar76PVx9AFF5R*4Wt`tyo3wg4Duq{7ES?_o+0=4>gr`u7^aX~coPQ^OCPPb2-=jXUb%4A`?>2cg|^?JP}=%<$%QVYM8yU%L~ z$Ze~YLSD;KY_VCsD*85UNIQbZWbCQRkicR*nl2;}BNIjyteN6?eMXn3SrOrlg8Obq zS)+t?*DW=B)WlG_AQ*+1EAdEA)JeWWUK_4MeeiI$7SeE%uJgk5SnFb-sTA}*7r@ro ztQx!SMbat9f!4?sTl`EdI*?)WR{h3!xSiQzhWH%m=NRcd)hnQx0B zF6$~_h!`@_djfA)JZRA{TCgk&Bb@Sjy1uTI+Ozi99k0|X;verlYBhW%dhl_i!1kC0 z$&(pt{hm)WE6B8Tqct1>?aEwxUB9a0(HlOZKZ)?-)h}>zzPO3onY4OAK?zn|_U4Dr z-NI~Wbn?Q4EbDbECibb3bK$@(PdpfH^L!sA3n zp!jC8)`Ubs?Fi$g;azgtkLd(=jTBCnY}ZduT@O8ZVHO=O4i^0y3KLa^J|;Xv zsm4GK)@n#4=d*5UAQky3)BqGFv zFG0;Im!)QeJLWc&E+)*h-ZP#I(Amcr4%cj=3bI6m4$X$U@xEBcu*^ZnCG$4Uvb>MMBXmC|;>V&?;wD9>jH? zozK=VTemaoKC5PCMUP5J6Zr(g44?UD=x)>GWH;rDKhWlMq~d!xxoXTJ9I(`ja->D(`*zN-m2 zXsA`WaExeJ$Km|TGah($AjiHZ*=TkO$I?_?<3{>GHEmV-oc*bqNTT2#3Mq$I5-h}< zYq}W08TVX|B@$SKDcLjKc~vUx%7_wbw2DwQGJYHacr9He({{?01C?!gGnx}AmutyC zoGuIqko)ck^|hkLTFbjyr>b^|SXmFDp!xN>^sR6jRK!IVUG2-AOT?FYU2=?@U8`bp z4U%+psze68@lr-Z>7k3jI{P8T$#&<6W2n$d)_5c}3!;%Gz)|gd9!$gr<9~&jp?P%@ z{?nj8E@?k@s&%;ilR`TwLB$XKgVMHhlKB8r3B0kC- zS|q>Cy0vte6-qgji#tr%gjpn6xXi?D8*#=hrXb3~2g` z164f`hv$Ra_cz8PnMXJ;Wu7m&-`r;vwg}x})2p%9xNA3ie%DM~w_zRoVi|KLB^DaJ z@6H|~@uptNE+&1|$8q(3Egq}se|W)0d~hXi+4r34-}$0SI~d&w`G47;y^akg;t^NO zuRNpbAVR2`Yq-C8>wf3hTk4@yq$}I(>GyH27oW|yty5?jyR&q2sC%U&gm|zHj6l$q zTz{>H3AK8#*gR}I*a2L<64S5BFJB{cToS;b;PcO~7Woy*hk(6!D@mj+LR%S=^%69j zO{!9bR0~;_arb-^rDl{~D8w3PJ#&LBRu|rM(^O6;IE=0*na`T1F9duB+X9He39pfH z&;KFI1^2evTKn{>igUaSzhYyqe-d$NX0n9fY6#6mguPE=P%MI)u8)^Cp2t?kN#_21 z-n}eoim>cD5Pg!oZVhRAem+5k^`0bdoO-tU6uZvhPA#CzBuvGDb}ZNU6tCDKShLm= z0^^T_AXMYBnd~tU%{}LXgdbe$99H03zQeJ?ixsmwQ?I^;JN*f3{rZh=I>SbHPxMQX zAE`%o`uw~W7zbn3=NHlbSTsWWt(pN`^Ay3v+h*v+E~OiTMlEY*pEO`>ti3ghmQJiK zreVD5ob6;Q`UrR1n`cV556^bpZNQQoP-#5>w9?r;Y#YPqpnp#Ze!X*vC)ge)rXjDn5!A zgmMz^-9^0HbWO) z0mzg3vp&p;%Pg`Y9VCTmYb;iP1ZsO@=SCFq?F7I=0i>Vt@88uoMHYYa@Z#-+;8 zEgYSetm5XYmfFi)H``3_01)vO^(**U*c;CjfYq0ffh^*E$uB5MtQ8L+08I=P@;8T?<#q(G1&LErk|Y%ZC~xRPHST8QoQeO zTfn-_c0t3(iOG=ac5c_B)>Rl$12cyobBZp0Ui-237W2{}d+NRo6wtpo-Ur0@cOMHl zXgT(LM`-)8$U#{LzHa>K*JrRH02kt~LofXSMF-zOLLz9kI?n29&U?K~DYMinLV$45 z;3P=yjg*A6Jo~&Iabyjr~4$=?uM z}A9p061Yt-7iF{8{5jj*pPo#yhlvK-A+D^hiZ zgEvHIxmF#2w#U8;Cg6M*o-G%C-k|Xwnuy1NuNRNahDObJAQOHxUsY;PL1Eka0`v?q zYtP`QRJlaI?q-I_IOHePSQe>kz&AA8EjiYRV}FmxQ<3eRvekp1^K2aNR*mH_sGoof zl~geKP!ZwrDo)VIdW~Tyc6X+z4x?lf-kYx?!AT~IQBRLLAmS3jh=-Uwd0k&_Zg{!x zIdHx4y=7#x!!Dhy07_tB)*^8ivKYh-_FRr-PZ0tj!(YoPTwER-lp36yyADGK zM5>kfx3EXbn!j78s8^TrY4F7Xqpo5#dfkr4QrKC z{K|3_h;(qI<#&?Jur`%tk((*o5PHXYj_LPQ~G~U zR@4yP#5ItyieoS+ixQ&csZ_;zBN2U!Oc$&)UC*CQvy>KFChxbxDzd}whcA&p+j;{x zA3__Pr&d-y>rhFkQB!Sm^}X-*5LVc>MCWz{AMstuBHu{&Xvce;$$XW|6(%F}XWoPJ`4jw{nI)OPN?m^DQjW z_gL1-1O1}K~vq1Kx5$)7ugGA z@fK$&GFh*>s?gmRJshiBY^LudVq6)de4ez?W{DC~_GZuF7LnOK}Z`SqH^&0&7KD0fLhH z$$S|eOg}$7`ON>aUa=bin$UFRPMEr@+XOo@p#zU{L1q^&f<%`;5C@uftqj%VGwjaZav7GU}bC zYrG(X1(pLCsLx_1H_v}yM(Ki48xuID#3yhOcSyw~vu;{dN(LDt6MeZf z!M__Pd&Sm7^P>KjWaQKtW4<{OMBT?r_H=IRj4JONtPdmO zSS{`k1QVVduMJ%kr3AHvS-DBAovJWN-3twMIa{wZpj4l3yv#XhFL-o&S zR!!!+VOyi3e`yG54uUkqr`4GDJReqm(>D|((YVac^>^pfbyin0F4kiqUsQhm!pQkH zMC^e*^*1j8cpcff#%fm0?lqyu-WbSd0Rq|p7>9xSI*YJScpT)Ga3bI3RzGsySq(9V z*3)-Am#+~!qMNo(V}1K44R^dI!xhayqFRO^bS$@pNOddbjS3FL_^gkaO-q;Iu|C^D zUJN=|PpIY~6oAxT8V(FBGA6Vwg4;OQU7Q%_6%tcKWqHteS9)LJs`6u8SpF{@v}P-_ z#vL>JaiBedD7!kFo{^LH?Pif0S@-?iH3Z)Pc-I(SsJQ+ zy55!6eR6R~&h58<+N0^7*FhtfEA9nMfrm7T^uq<@X5)MeBv2WiZ2DN;M*0*8Z)}gC z&8DjbeQ7nHmGnl_L@KDwRICw61brQ9@j-ql9Xx%GYEEn;as>*f{g*oKNzRrywAVJ} z_uoTD0sE?|EyG?u^t3UzZWQ-hsTfXmijs{wkrttjg~$mxu$Oo8F$D zWy?_+Y^Bw`m*%ai@xP$fMT{yWXluH*I%gFMUowUkmrkWxz`~m&Ppv5Mj@>!pXsuhy zD*z*{Q<~=n_9fFId%(dOomw&=V+^^hZrH0Gm#d@ZtJ=IFYt0VlMcLJA7brzWqP7suU0znl;HeGSihO^ zMQc05gxRIRpIUGv|7%_`rf9z0}lR4t zE_TDBXs?jXT?2(hYPfwp0&_cq6VmAEdK%w7eYqL<1=FOkq&#R-zu9W;fKk)ercH;2 z+vM8sGOZW!tfzmw)L0=u!MJAb$cBPoz2G*GfLqdC>R7+TpOXj~fEw}q6oNH-l(Vmd z82fND^+3d|)Q8+jGy;yai3@*zzqZeOs#2p~^+2D8F67wRW?KN*)fN)X>ks-ybEJ5w zmlVZ={qA{m=k&)c8cC=>WES=yY}AA(*Ypi>+U}+|q#&Vd;#HrY&-eM05&oMZM*zzB zz0a)~XbV;|8T7C)kN;>Y3P3Ig2=n?f)&FJ!{S(rnQ3Q8q7OT??^p$4$4&zVpkJw>k z={yZ5sdTYySuKauH${jYy`D@vk80lyHp{>*V?Jd-a3&*IXOz|N8L0~@;S2ThG0mRP z+guV%PyT4e{=l6L8z6p1{6Dk zN2{%etov6}4$7^q0+hW7>;2m!_*Bl_>V+SpX}f4eG3Zj|k1smsb|!PUr;AdAz1CN- z%(wBpmJvPSY;PuB+nomLxMRVBI&UstJcE8VQ_ahtaW@dPuflMwl|F&nut2Sbf1Y_! z)Fn?Lkbt8YL#pl>nG)8QtgozPaXAORnjHgd9A-=BdjPRQW^2?i*?LtQ9PxqX@OCbR z_T=dNoQ+qLoMHz@{il^lVbY48c zPYMXcqXq~G=!ieI$FnvSF8cNI3LlXC*u7q;#9>J;m^%yP7wbBsxt(ImpPy!R^Cy{t zyw+*?4BohIJufqzlPl$EILTvh_O0&T*myVjvrH&Yx7(yx zy39_EG6Tk_MyK{m@_i!d)s@i+*aUHvWw%2o#k5!{&D?h!Gk<)^J*0YFuSb)ue%>OK zJ(wai0NB3Wn4&GhFZe9Z_opWoUuWi50oNehRdjeeSIN&ExldaAvPfW3#%?`XNMI{I zV7^4LU_P^-d%48BqjO(O)qZa77t7m_umNY0R;d}e(#1Zl!$-j_e znGcG!g{CB2hwJr~md&543>EGd$zsk6NP~$y#Erm&xN*^JvL&F2ids~t5D|Nbww4;T zIBkt*L{5c|FD_sOmwfi9+jP>Q9XW3`xeA;-rc%E;aSHW-JxM7uQP@6Agv%U{+m9Zl z0XrswFkYgiNybJne(cP2F8ru18B44Jhmzm1VtI8&{Qk+jbBR7bJmPI!GJ>q&;GR;=*i`p{IOO*%-t+%%Z3O#(y*4x{Pw)xJ>S zcDZK^lu9pMm;q)YJ3ViKFK+3pyIwtAM!pb(-Qk!rtkwSN)nF>L=G=zvy3ID*OlQhxCQ?48aM9Bwa;yF#aPvY8@b#WB>m>m!^VzH_}EAw$-- z@J!uuw0s-h75VxUK^0K>OD65@WcSUNX4da0d=nabq~zKAVuhJej&~+&LL7t&v(DY0 zkiOd)sLByvFM!-UZ=E&G9*C<;SIjY0#r~Lnlww}^%AZnxv9g50?to>oQm@8zFwko# zJdStHyjFS}mSQJoUu~{O%3$3BC?>gPB{tv>Y+EF`%tvE}#!fqg;^Vt>CyI3tpMC=8 zic<>N0-aO}sJkz>Cl0TiLnnps%6JtQe-o(0V?}=`Cc^B@W-G=15BA`UDU@d4>z#)V z&FBY1E9$>3Gfh8j#N`?<-9)G6Cu^Yg_sB;0IWlvA!U5&eSb;_G%cbaXNlJg;p z(RO1*Fue%dp+R7ejdYR)^)p<_Hk)Ok%h-`?O5W%rLj?JMp)`5;!E14SCJ6ZBiUJfN z*gpr%m*1{E;&haI;`^F2!f)_BXnr4ehlfHG*v2VT@nKF#<~dV_1|?DZeL2VASWS(oKP4Df@>!Ckf-( z{5sQ7{O>WGPvsM*syt91-><1W?Kx++SkQh%KE2|Ho=WMO*GoU^{2{t{_2bD}FTR|t zLx8*)0HWXd)9noLW|4El;z?8|G|3CsKC5+ent8`&6i34(dj-b-gD7 zDAO-Gcrx>?+JhdSy~qo>+2K}1E`(Zqjo~3WrmLjk&F}hYE|N= zCgURk%2jS#LH?gBxa1ev5o;#5?bwUp{ev3dWMy4k`A?e}n7A&oRGfX3mWcZ2=VFMKNiV{x=%-?2~#lu>E#!1DERAOf^Sngl}&S8P1~XZS>d z@gNLTFpzmJ*SosD;`~GC!9o8L9PeMb)?Y9(NjyOG!l+bn_y&|{4bQIvtv2^}+}TRy zrPwL281QDmz1Vt+%QNKi%3Dqml2MjS8%=I=rb;Kj(JelxPzT3p47gtue*~b4%aJruY9CggxJv^(sy)r5z>@ zxVrI=Ow1lN-blvGTaY9r+TRr7+UssU+$>r9aMjP$IgHC=^Xl~*reoX1O6sTg*L&*O zYE2Yta{XRxN(VxSqYfGm#5p0>5*-%*{pC6f%bU)Y9itd-mJ<0qImq5j{jPk%PE{Cp z)DeLm!)O}<_DN>FQv$EkiEmdpb^iEtu`9MEMdt*a8^zs`$W=fylV(1Fc&|gsy7?gaMca1>E#2zjN4K8 zgV*w0pVY4M`XGEg1GCkQQVg3xxl#Bxr}ZHiCW}Ed(eGqwfqT<6KGk+xBrPJLFUSED zIJB<&SVV@rzs$%4=f)!jiBqX4Gb^p3n+q30x-Z0i1y}tx^dCj>zo3$QfpS#u`kIvB ze0SSp@DD%5NAgRiyYShRP5hv}#isR5ywZA|RX$JY?rl0YsYGH^azB(~JxgAef4P2d zl%y=BlnoU&&UfFpy8Gx~RoVNJqq~~lq)?u95I2Csv9In~iO+5t&G^QglyV%-3;*6@ zc0}Xt5z9UPvCup9aA~jQy8;dAtR-Sr%v$I3h>PKq{9NUL2)PQ4$I2_f*PR89uy`y6 zxk|XxP2LY{HOGM_rk-tFDIHQHf+2DJ8Z(ozHB26sGeQ(3MAqDxURgwplfyQ(ka=KE#(_hvWtX5Scp`sRsFDIy>iQSifT{%V^cTO%!b{5JEe_E{j8 z10Qe5)WWsX^4RWGT_2H52m#N#Ej+Umw0xFYyB7VW?dEm?Wvm`fCR^^0(r*f0ngUe* zR)#&_>B(9Ui>--# z-_1dHsk^MMVFVe~!zUU z2e9$fIUJI~Ir0B%;05vVc`ftzYIg^#7bD(T6y`tJm~wVPcu<-TL+;R){*yl&asQ-6 zwxNvsxCbW$x|-h`-jP=*KuYaF zY!S<|o1dg_i)d!E$JViyoa-Bm?R5mpRb@*+gY^DrYal;}77y4k{hu+VKP|Dh#xjY< zMQj)|n@M)on=3S@L)sYc$jua&?XUwn#8tf!d4^tISLQFe)8M%00?17xwSJBXNo~uO z)DCAKWkuI|G^IC-SE+>i;)Y5JSv#WdyCWI0R8|pMH8ebiM77Ky#F?{m|| z@?7R`Q0|YtG_o(669yXgpXgGH?`|&;P^xmv);*2LkY>M=t7R)XzsZy~!$)@X5VJj` zJzcAMV56ALmp*dj`k)YfxMVD6_?fvZ*YZ2t!DU*i)unTlZ^~w5zO})dD{{>lX1MeO zfd*UWbW7onP8m$lQ2xmNdatfklT$lKvxY*wYRSzk%!}^oB`6>;F=BZqFE~n!+dc)T z2r>Ozj*I@4<81#a$Dy;DDovH`e-_u0`s#J$D(rGHX16yh1?rK|(WBKqBNcT_Go!Dt zx=o+`-v#bwT8*~kmlS2Ky1t0po}OVgT`9P-oGLdL*^&5wiC3Ih1Bi@A)%s2?l2BhT zgG($8w)?ncR{9`tVpo`UTYi?Hya;=4B*XDz@hvR6bBCTT!^%!j+%9l_jFihG;<8># zuSe2ovyOX#boPphJGcTkpd4iKY2I-DL364ZL^ns(BdM3?ay63NvyUs@g#098-Y)Nq zAgu9Yqd8#uS|{vWgO%M}4Azp!=55K(oMmBs;I8?^>D&$Bwn(b=nP}m4c49d`;nh0$ z&=omfe(ZYMg~@DF*z4Rd#pl3gWPr*XII8EeJVuB?r^1YzuUy7B zTkF`8r1l=@3s;A>ADNsUDZ*}vvTHPGlP@`Y>@Im7k7U-H5#^lDdU0b0_s20MOuXU6 zcdph8smSlaC(ZgLY46t;bJR<^)Wt<##jQB%my1x69Lr)(XVZ=rh-Yei0&M6zLQebe zmYF&e%G;5$lBYSbnQJ`|kxgGQ z<47u%&h0g*qrNn|WOKe1iAR72Oard4Zn8&?>mR0(4cUJ^EWYj0RQY4He~%-N6I;GJQui$Q(iVeGgMGgKvv1J{%a({;862&(XqVaa zJdEQy14Asa;?GTn$5?bp7%yeIKEibb*$^ye`^d{%V!sfTQ)zI_IU)O5rUoxasCvrv zqOjR&&#x{8wDZ2R+cJ&AWz(VnJs?;pO_U zP%x1gZL6;AI7V>J($DdNd(37>;lt+v#p4!p6t%HJ3)C{cJzqbjc%_13IN3iD7G*=& zWIUG@`Fjik>Bl-4B)zMj=#-iA0Vw$R7c?lea{9|GSsJ@|l63<|PB-)J76fYr@nC(j zk?~+@k*U<}qO(!Z0ZESI_0t4zn%%Z6-EN>04L8c-p^QzDhpmbhkp-_B#r0KeTr0<6J45*p-(o zS*GLoVCFJXeIx<=ZW^hX#HVNwxXBByYMYzyXZPh&#AxSDOGH@wCAe+kz-k#u_CZ3) zakWd&n6~yFua15lb1nk*DPA(-t3MR_!1Zew0T0W#evwnoX;39{e!BQ}D5nnI3tA>R z+2SbrDSHKJOfM^3`MC5^X~C*E1>h(1xvbLeW>v?i)0vdPdu{5gpw4@S|H@ZMyz{rC zZ*FZ@o;$_c9Kew5#hU$gXu^Ic$#~sS-|W#~#}Mro28tAk z`ESi8>;6TJ=4@aRL$FS>0t^pl&zO~zzv zFAi6$cw7$noUrw!cCruCBf;YO?(G>-hmi({!>^FMU&()l+veaiq+jEdQYuzt1_s{( zjcQZzUB5lnfx>%E*E6+EBgk~k>A2=lKwY(nN5jc0RR{85f*1ZfLz8uxK^@bVM&Xum zf(K%X`Ga*7$13AV>i9m+xdLfjC%PSdHm6&d#*?{zvx@Y&Si!I#Z%5c+&NdzhuRf`8 z48=B(r`metbTc4J!i$+MNq+_KGp3>s}2H~kS10-Y?eJhlkEQLKUi z)m%p~(ZGKYi65+ng%Pb#M{ddcy#X)X z9XhM-bP3Sf_>6QXP;%5uQKz??3~^!VW_=E znJ5RRsLlGB8GX8H*%nKdZOxfeoM6awxhYn2W6oD;r7p^M%d`u_flrtpjQDT}WiCA4 zSc{cggYlPnDn)U37t8iK-}TZN>chwmfSjC}VUE4#*s8xmIt>eBVw`nbx{&qM4yV9d(2d$pFjU zON;uOE6z^qROJ*HWV5GuoN-`D0_CvsN#F4%pheRh7=RgU8=tGxALw==U^MYbdqJ;> z29pC(0*~zv;9!;ld)!CH33;%AesT2zn!e?HJUwW8vnNXygVLPqio!thb^m?F{2L*L zQ{WcZW|E?B!6$KWc_9`#*wEg1xsztQx1&rjLI>);k>$uar?|&A;%m;aLGyaSe(R;i zux{569mk23z|Q;iL21==uJ0HxP^ViP**~fV?r4?5LNoS-AYI^Qv8l5A)B@_s9M9z$ ziHu!#%qV(Sr!EM2MrA$mSm5^-pCM3ohaj zAUIN&Bhi_)dShLm>+Eus=>k`h&IMv@8NwV2Jfr&?ayKTjqMj5msORn z(^!{~Lr`dh0n+CRhICE3PE^n*s6SIKpU zhV|;O?qqNG&DLODKnsji&h=a8zd>XF=@TPn12rfRZTQ*urpIAZ{y6cz@cs7Fb!@f> zln&~4Y%f`Lpp}Pd3L{sbf6rcx$XAo_LwlhRy3@Rq7w+}ilphrEb&sZO_ntQk0N?v% z{ED}DF}3+}m5PdQVy|ZbE!WQ6vVrS2S~Ys6;jrSs6prd_hTt}9)={W<;CSXwBSt&<%0UnX8kaP5!? zb>(h_JU_2`Q+=Wl_0iEI{l4ge0EK95YsAbfG8^+{*u~zfk)P=1vD>2=#F&RPYsCca z5?=*3LWp@kQ5npX>d-cB51NoRkJZXvgMjT&&leEfnyAX5k79)c;IW|xTl36g)43U% zQ4Yv%8!dujrJKRF@Y@Hb6iYQRwl_!mF`)UK9j?5$);Si8#Oh(iEyUhCBjl{&LUF?? zhP9Adiw5kl=puW9f%~DJ-;EBrC*UH9a(PrUB%;MZE?_YeIwgn)tnx6UN^-?^hC;u; zF7US~v+zcHVVYR-MQ1l#xnU2o;>DDmAFYaAo<1ru5C!H>8i#7&4^0nW%^BM=d7y5x zoK_Je69#({J(Pq&VUoIRX)mR88H}m~Vpnfz#b`*i+6{{0(cA=+As_Elp?RLjo=++O zQda}lJ`aY9)pQZb#dYL+!W15Np0KC&AIk7p+ScMNKKj(&?J>I)J--n83i&rJ1m>3( zBLDSowUD)8N|jPWX44VF%@4J&^Nc5AJK()vF=E) z==Ug%R2nfw?}(SSLHs);3*oiz>zyO16{&JqmItLVT^wb;c(uEEJwAU)E_HQS+5>vt zGM(xs=NmvWnj7eGc`et`#q67t?HE0^F}Ky{=C^r!LfCdPKe=SMlRhdHSM9wkh?1$YUP5cvWP@A?UV*{118_*C*W|fng1v91Y zzZBy*lbTQ7cwnpA+dhuzZ??y3Ea5nA?sd%e=^^!i8Hkam;h4+{rp414-*9L2WMoabP=Ic$-f=ZFCDotMv?8iNx z)X!I#zNmY(9bb!5c;M2Pi(6`W$E6!##N~2vP^#ZTu^7(b-P2&dPb5#heg{#i(Plby zJiLscRcN(I@iy|=N59liCZE!BtL0Azr$D}cO|NX55d`8fMm4X?YqzyjDD6?uqOh3d zUWnRJ#i#+=6A0G};eENAj@QD7-`I_{-O1C0+g}L$RWVzH*iRWdVELPh_Ccc0ztTs^ z23>8aCZ4+{i1}T{j3ZH@5 z;l@5?+Yg>j)1PQDoQ}!rguy_~JL~MfIik{-uX-B5T7kfD)mM+8b>h@#J2Ux5(?o7_ zxFrB`=?l@gi9IeQQ-{SHj)$Vl>2S&GEq4jD#hO98c(h@cO)RCY{0KzR<)zMo^WDy2 z<=dLE;vBHqR625x`=f&oDp9sc2rJeZ+Rbo(9hGDpJF*Fudy!7A1M&{qcEq4i5N<|8 zLX@M!cvsH;v%Po4Y8|ns!(ALiL;Gq9ef^*Fn-mUK&O>)=&bzw%`^6#n>TLugteuuH zy!iPVbpm; zW8#ZNpQZ(1r&-P^D@#@!5t0_VqZ=;gjwg~nbniHjrx?4a_xmwep6m}#@4m@YV5pq0 zDs>x8xe8omI5@eQEC-_Z*Ec<}5|`WwK@BjAJ5^XS%L4BIa!}NZfcDb)TKcI>P@TMA zfcv9bCI$cM(W^-qfk&fAEokTy2qEIeAbKz5wE8nxkX_UDHXPNqEsb0lS?tl5Ya;0- zS<*(R_LEKC3|N%CVZe}iIUH#gNO6OXmN0PoUMV{8^3ZtxpdGs%p_7Oy4Fijw{RueG zW~*HT+^{tQXZ_0M^cgE zohx^R1>LjNJrHhd7>Q`+CS~c6p>+lo(Vw!Z-}HPyUm%Sl?Rh3d#aHlTvUnX;po;3D zZ_4Rt#V=-;wFhFP{Hn8giQESTc7w-u+AryIwQ}3HRpu`1nOQ}02U!f~d_8UE61YqQ z1uQT9o|7k+_sde){RtVMt(ToJ5|~Zc{8J?&gD*6=Dfj5+zG0%ZLydxQ>O=bt30B)d z^QJBefD5;)lh7rl(L||Nbm(0$SrQ%DHoKk+J9oI1WBuqjRbRt#SJYtrL>~z`o5iC5 zsiN0pp~OI{t=ej4x8z+NgtW*l`&N~3jU$3xL?>h{H_=JBa4GKM_H&(%Z~#`k`RtXL z3+{@*=LEB+(>^^e3mzYdX*)FDW#tPEY13fq^Ke34e16RRkuvLkfB(h;WX{30TJBy@J5cbvi~YwCqo5aIPvLr%}gUc zsKAv)0KXPM&4~LVBHqq3F{dIq8TBrF|C8rEo!B4NkeDh!r(v_x>Hn5RVt0* zFw?*W{fDyD1g|_1b1qk**amlU2(%?`ZhU`>Rq9XiFBd03{tH573;Z2~I^+{4mIG6G zE>hw7FtBVQc}0d&neBm7k0stYn$3V!f`FHGgY%>*;=#75-Qs!Z@bPB!kwsmW^Na%t zf;TV)t{IR}c*?Mp_E@ck2{6M!Nd4!NtZT{HNs4Hb_>!7OfZeUGE#xQh2~VELbE z3*gUg><}OUl$gQ#refx2^)bMz?ku(YmK?R)=yLxbHQVoD1E8rM;W#cl+o_{;vN6^&<1Zk~bX*`XA=&d2k5-Tq5+>UzrD1 z=PHpJTpFdsU&*gb78k=`KL{LeB?7^3phdmsD7|>qZ8{{o7r1p@oK`CP3Lb9_9*bS-#){>4{M= z2v44fT+sjB4Fi~wfk7$&eaWC!X_3Dl+?%DQB;e!7s@6P?k-4<(zU0)v_7%bF{@KW59fE8lP3X9of(2sfl&F|YdG~hZU znDqNo4i;NN?{gGwz^tyTEL8)iql=<(v!t*4sGXr~#AW*vBFKioz{|WgE?v*W&LH&l za`Or8!%(Pi_m6P*;l?C9r);;SOZHW}?DGx!A;8M_#;CLL(FLBLt?wY(Ki>sDIPg8W zpc9=3UvLFGKSC7zW0PK321YO-+7XWjwi*7Lb^7o8)I8=;KtFj?ZURRB5TQhb_WoFP zXEqJ%eTKmm+_LOYD9&jE_2<-SlKPc4Jlw~U{xx*|CZ4`XMScbi5{Q+`oxRD298CI8 zsvP33v!@H|cdYwzqnVx8oDnk%j=M6kxZn-I}1-22eH zXuR1sRC@EO;AXydn2h})Abx!uevdt>i)Nrg{BuM8CxHIXe|#Z-_!?yzV17fkYQq;S zq@fhHx!#9vR<8Gyu%rWWvGX(kVjW9q+%BJ-ZLU zc}*q=Z1s1D*z^Qh_syR0Z^7Q5a3ijF$kq@2pt3&w;I$j1m68~4{{MppC=S37FiyK0 zcGt@`Yz%HE8@@-s{=B8=ulS0WME>}{F75yR@8?y(b5#&zr6CEho4=FXx1IT2E?pd#JNWm*{`~vaj9z|z z^2sX@Ipm zMk~J0>{fY3ecvorwy%N-E*K2vT>p==vw*6y>)Q3Jf(j@dN($0!K#Yio zk^mZBz%p)Ze{b#o`e6R~x36zOM)}5OhQWZaC9YVu3>K~9$?3pzU~Na|@wygp8|C%f zd?M6;H!QQsHuU+QEu`R=yQM1k>>c;N+EV}9i&OL&T)Ln8Wi_k-3PiaMQjE1kOm^6z z1ne?luK`{5(DmE_HBImPWIt#>D(o##P{U4bP#pogKoo$+ zq#1P^z5tw-pr@y&gNzINY^gDOBDY6e5|67yM+8+S_pFDgXG>6Qg`UOXW^8GhOvoN+ zo*jZ|f)dB-%$?6#<%{(ZL4YTvxGJPnb+;oTv)v}DGm1vNs!o`!@ zWF>9_PU3&CGy1y`(&)FPVHabyA8^~B+~@H;A{$PADTDsU5#s&nn==hxW|3agVDRX* z!)2lQt8wL`G9osqC!O@YG$E(S#|7vdFoe!Mb&c^GjJ<{>#+mqk31#_P=6fXxUTE-{ z+jBzQVmk`Q%`x$?*QdLBN+~ec+JPd~eSB8)ffmW=&B5~aXaVk-LRsDO^NqqXa@RK> zr{GVSJ8b}Yi`wA*qeBCKR42nSvGw6>(~05iNM?%QM8eR?^}%$yJ+;K~52QX>cIlm{ zFXor?S~+HFtn7d2*T1sZq=XCB``B@4*K{GTcTr+97?~{l$+BSv_4|am6@(7 z^SZecgvUq>qEGx_9qlM(D=3_7^R1nkebK5eVR7ky7W9NEH7?IzwLn9dh|^(s(d?Pe zleS3l23BIpZ_$B!18PX45qnGD0F z$6xNzG3fUe}$TQh_;uQAw22&jo1eO zhkPuFrpuc-*+=ZIK`+$B+MymJVSJfP_+xk4+;pe>fRvLsmn)oz0v|^#F|dHInuNC5 z{oVXM+;=H(wUbgcTbj>fu%>t^2A--7wP!I33N7N#n&VAZ`$M}NuJ$~l>nQ_p6|KVN z1-ybv>Q-XaL&W5p-+yv(`&sQ?x!PE1eFVMxY?lcmfE=OMRdM_}zIi(rzIx|gY3f_?u2bh=k z6cs=&%};eFfMMck&iQJASzE9#S{Jg$8kba_V>Ye>~!l%ceVM&--W zEH+06c+J$s$jnw_e5w*Gvo0#az1du+4@CTFG0}=;?rt-S?|!K*mm3OcH&;nSG|+at zvW%S!zZ~?U?+Tx({OF`rai#tB7@OAuimzNjOo7$pQt^N>c>s;hSnOS$2C5$VsoKNg zKApF;uvkG<=d)Nr7Wi1wGa@$=yfC!sauLsTVVp2$HLmCa?ngC`SuSS-C!9ASg_8hs zk)hXwbobHU=C%K!$^}=6SNPYhF#^0Lji6U;hHSkM_W~BRyPBvT@NpbksF>MX6TK6J zLn}9GrA^&{9$H$Q1smI)XYn|H^psKU%Ug73FLWFbWL4+CyxjePHe6syppU^e={oON zTDhy@31*%G@><@LP!c|w_vOjs_A7EbGahe-yI+ElxLjG;yA}E46RhUcij?u0_A)P# zi%J1Z(#IE_2Tk;U&ACP&(~5|}S5T&O6{4G%E&Nwc|7wwm^`mE+{=vY5!<%e#*b70`KA2`(s} z#bQ#XagOhIGFdh{e6G;u#G2dl*o54LExeLrlAaP`sgokAU$$+u%*c=Cg{6r1JzU`L zZkIPvDMuqP$7f6=Z*-Z<(*$hZt%J|0m!`}elrE1=DTEfCc|S2yL%FQKdQBjlj;oO; zZb_Qfw!D1()dd$J-Op*>WhcLDwK?D1iMUO(AO?3ySL19AuCvg%(;OLHV7-&Pc2)|1 z<@$8s-{AC~l7kY5z5xQLVd|$mdJE+EV4TJ#kMKTrnd62LfXgL>kU(wTnvG`D{n&JL z2aHN=-a2|&BCD>?eEk_c@rHvX6q3{%Hzl1Q%Au3#WJ&thPP$6eJdo1liWevxKK@N# zX5Kj~HI7{N8b9O|k+1s9c(YS?P-oH|mZlFdPR->BUnJadfy-q0^3DqyW7I z9nX(U4WMLqT7ohsa{i$2NWMre3Gu+u?>BRfBn8aY&_}!#;gn)OjeV)SsuoTKM~#a$_b`KD8qw zr-wke@aCe=vNS~-n#7}SmUE~qbAB-35l2zb!KW|yt?dnD&XAOGvc|s6bUkJ34)Jwc zFWEOLVgww5C2IcV#v<1jD=V_o4Kj@u>X-;T)z+Wd_Ox^QOlQjvz+Cn;20DNUi|+mq z6}L>AS~*~Sfz0W45Oj+X<`ucyq2HQbGk(w!dcA3R`EyA0l^cGFJd~J6|4p3Y;)9O- zgL7Ahy=$T~Ry5BOJ@X=$yF7bdG(TP)DVC|(?ql7Ip$0L|(Cgwen_I~xl1%&?$Z6SI zK?jL~vGZ2H-zQSuGrHOzp{uc7)am2G*rh;u?)zxfn zb9^>?>cY+E{^PGX$~o;8ATQSz2J8wBFEvXXXdJgDq>YYNXA<$*%&32uj~1i@6keCj zBwK#AG{IzMSE!|pE)Jb)MyatyG8G?e^;t`(`2}J1x-FOuv2t5R8aVn_0SnXdhX%)B zX(j8;H{<(;!`1exvD}7(lei{u_n2i!oryZLc<%+)| zes9Cwj`6_#feLb)kH>CGmEe)ikW56Q+&A+;#(27UmsSLE)7Kqw=eu}Gm!Y_cQ-y69 zprqdo;m^Vj6UA)WTW6bxdil*2W}?w3Cm^ttSnwdFuTRMzuH;|zm?dGbFyb%?Agm90 zq5B~byuaSN@%P-;yV%iNN&kra&w@fq1lR>}bybz2+u}n!ZkIxj30R8gGztU|wFBxs zr!`VvTzWm_oB;47NgP_0$fr3j>NsJUve98|R#S6oXC9qrD0o>)*@Mg~`DV)aV1++I zd&(I?i6qZD>5X_m181LYFB%WmaJsImjs%Hubt#-MG-B?6oQLnKxk`ZKQ5>Ep-kpvE+b%iJziG0`zaVH8cH9w$ov*z~&{*7aYMb!@>xBoilp_BfS zHb|NGyz3NovBKauO?#+d3A^DH?i!-zB-j6?UF8*Dlb5`M5y2c=BEM*j%n_O2=)%o& zIX`e_C_|m*3KHQolJYV!>B@^`ZtK3KIOTB<7j)qlDE0tEmZrIecK{=J@qB9dDyhw z581VG$j)z?MfhYxgiq&xytjXi$!PYd^TRVFVJsb45)>8q&h#e(#mP6}S@L8;N}vx9 z3R9|MhMjJ*fWn6@VT3*E6@up=x3v1b%NTXd>~3EJX$dA`Sh!Hna`!V z4UGv9T&yy%A%g+tosd&rTNM>^O`_oO`tIdn_cgyKJCalfLiIa4!R2ey7mk!UYIu!O zkqP=DVPtLg>Qjs}d217D^&ED|$t_``u*Ti`zN=|$f)cI0xuf{LchUrW2lH?w-Fu!4 z2N?{ixUaORbaE47b2s35VU=q@?!*!b&SuXq_~@2O`t5|zdUvkcW$oPttD1Y`9Oda% zQ(~5;W^xrb-)Kl5bpvW?Zsn4N8FgJSAMQ*G&`w&$e;De>-IQ5Oi@+=yqc)8I$H-y+ z^6bkt=GH$0(cjA68gdu9=D|syN8@j#`3rA=2~B^1MhyL0D?zQ4^-MaESr`}OSUiP; zk^9E55>&3>$zH2~;noFHhD`6>Y$hVYutf=4bqT$($g64v!4Qat_(8Qp3MMpVVx}qJ z*d!QSTZ?ltx4mukeJkFXp|;!dn8CRNkzwAoan`w#>m}FAi`nt|GLISK#)~9i*P-nK z_CM)8p2f*cgS8UF;nK_&Y1nXkW3~iNFuFE)8W5dxjbvY)`7UzfhMVH%tZL$fCG$8Z z(JJSVqak&ZUQHaCE0uh0$p+%f%T8+GX|X~3U6B#f4UCl9u+O^;j_qZ|p?l08xl06x zUvv)jJZtVqA2LcuLE9dDZ!bzD@FLI7K!VEGl5hykA}zx;xNB?A{W{IjIK17mG~ADO zI{o2sKDvv0AHL~NUnRYhfUR89$#g}xT0UC#)JJ<*8yD{^32SU)?^~4N`s7@p;Wep- z9V2$F-F3*9#lgUsZ=OqTnp z^-6Ika_A|TQ9+rp3|{y=ec&8ZP$;!fMT$f41g*Gs*6~b$u;>g89V-mB57K}Svq0fb}?SZmOmpzj&--rr?2vM0W07m*=$VCVb8@O`4*vkM5T)4wa*)pe488_5!A&}=C zV|dGnY5d*vY>S%BI&+*#U$N6lY00m0YWc63HuxMT*M)ghd%pyu?W|w4hOVpcD;!u$ zbn3HyZyW0!_h{%l^Mpg4F7S^u`DTsqAor7pvP!vitqw>VSPAjajRHkOR zb~AdoKPn2oNV4_X+q9fBi~CRRYch2E4!E||50BpHOu**JLoqurjRp#(h&mHlqVmkD zZSGRw^eu$>(FKqvjj8nPobseN1zS>SYO*@n`S0xDo=GTt$6mWf*wGiVOoi4Dn}1_n z(J&F5ap%Liitp10 zl^b4*CT%&hIe#T|X+oYbwyc;Dc*@v#6H<9e9Zy62Rw0zu3AF)rpA}E6mE--#oZd^t zW`X!t$qijJ;}A$~2v|TI9{qDYuq_6(B|KdmWoQHN%?A;jM9j79RFg!}YldB3LS#M+ zWlE=knH~>fUQIaMxmh%MEArkO6kPq6t_N?jX$cF}itmO#<(cR|A7ZHWPnS%7g?;kig*)L?%)OP3w^O(e^oCGuyCBrR4Br)?co^#&Oxn1SG+TsLhgU1P+%O-bn7qMpr= z0Qali{3ac28t43A{HUXLG_b|GqRDjr6qS?^uV6@{N(0&(*7npdK0DuKZ-tc=%bOXu zhTsRr04oaOg|!;L@y;|ealwvxlP$5qkRMhv8GmU^iB~Z8vm8eciIK0D9YZ<7DwD~Q z&;pKe&BspI+6bsJz6;av2l2+ZBur< z_JV8Ub@#`Ng5w;UDV{VD`lSe+{H5(t^I z1U#l6Pvy~{uv&B>>!;M=CrEuW_c4GkzhovA&R4CZ8nsO0+P$f@M#+ad^5>)OJ28J` zIc&u5Ia-`#NP5#5Pk@bKopCBt7wheltS7w^R$- zTAlj57i;`veczd!C-G&KRnm4qfn%GuwPqhIKlHeMZk0IB!zHsK*NY$-&CT!TJ=eoF zE?frdD4PM*BB(9A#x}=pmP(c>Cy~CinA6L2+&7tKwwHi_ zU#ozqFlej?v%Ab`d!OfZKY9=*E3yZ+b^hv!qP5$>ael4T|sdG_zE)~leb^kFwqzfdH#prxBoe}mp-C4oR2tn0C?R4x8#eM*k9Hxz* zlus3+WMm8$Ob2cd#ATxgvke~cLD=+^xwNX;Qa1z~3RX8$g?v3pJepR8x|Lyr8ReIv zuBE)!iOhcY(XORcq~o9;C88N9Dh1`Y-G*7Cl_r5N5B+k`Q!F?Y3jl=~M$YR)3mxkbE^*y%9;3@czM7{V9?9C;;}mKr zPFt4LgasT<+8mMxVVi?Fht{}pp6KG~W(d)UCx%k@UuVt{MavEaS6Wm1zwWX67Kc97 zZK;Vf7XAm=pm|`1$gqL&VAw3Mww&vCd0TbF%|3y%04Pm4Nc{Vs%&_*<~Nb zy2fa8v|k!bzu2kX9y@hYr^65r^2U-D zA$R@di^Qt!A2}=o{&Q}g3{Rd%i0MC7$WDQ*z!f4m((=!lDx95kaV!pAT7TN19AX$o zAX?Oj0P*)%+V_4!>@D$l5bH8I?e-qu`E~B@bytP;`X!KTl z*r@)XCWIrrSI$K3G``1ovhk3#?k69~sO?(gg7eD92|s9~OU*`&%J;D8xO?Z(xM2js zi;Wg))CFxgmN_c3u6{lqv&`j3CFxD{9z$nX`_7!k3y~^)udk(--rS)-)#x+pmv`>G zu^dvEaK7?Z$@jA7avB~@%Jlfz8`b1l|Bk?FxIi-;85=@w+-iOAQudPM*Wj>!-Hy{m z0Xmg7Ed3r<7#P78ClO5}2^m{?!h0#3qn`GD1cJ|MCE$LdacwXiJCx}+Sy^5*l~ddi z_R9*-xpi=q-v04Qt(1(&n@kac5}BfVzX7ZR_{=ap??M4~@#&LyqF3Ao;a$-~xe z+<3=p2hV$Si}-&s>efV-7?su)aFrpofzceYPh04MIn;a`fyqAzo95FLyQfsli+Ar) zFBEYFp;ljgJb-F{^80MNU5c;Gfu#)f)T~2nRuMYb{W4Z;(6#P!aiTcdi~GP@bF1-9 zHSM{1sR?Frz#y0CEA34#{aPEO#={N@tweJhzeO|Mx-08?r<%6|?muT0KA2e;OsV=U zLSAf^yH#Mn&Dm0Vpo;77q|2n8pONfWB#k9nQgm8qnVXj&t-jeKATRxmNAcklF3nx` za$tIfwSM0!JFP05XwKeBYNClD`o?h=D{OKD8VB`fV%1!QjT_d&bDpa7{(#ldRv6vF zZbJ72JJxFg_L4o~P1DguCXjJsof$RMRZ81GwS0c{*jP;n6RAXTqkI$#Lu5xol%S8K z5}#9;Q1(1mLs8pi%=`$#N!6UHk)^N75MSl|JfFi}E%)1rOdrZktORx&r``u%O)BK}CQ0^+z@$)BhwSHbbov;=*?-iD07%Ma^rNG_!YQJrnz zeJd>s)<{}H>i$us#r8V&T zfd_ogpfQNa8+_iG{$6%Zs6N>U1mb7=#(4U=+_#URFwL@BuE4bK%;=JV7{TW*Vp~qP ziJ1&qQyZ?lJ`TYEPVh1D>v5+OR|C&~z4UJXb`BYw-D^oRfU{vo+WYz zA$AhnO!5N<$cQ@_M#}v7>=240k+l&eBtFEf2hb+8<-2}Q=G7Nb$j=+6ZVE<7-8 z_(hI#X@*XrB@8>v8_Xw7>4~pwPwj}Hl+RY6SUv`$TCy{4sx~!v-G5pr-r?d(D;4DQ z&&;ISnkiRwmO~2zqi0Gf;;a`I1%b@`aq(>Xikw)kuU2M@lf_|mXG;3*?sR21K3iD; zbBIO5#kRW!S@RCzCbYMAv32*rok43N7~RXBA8JMJ`(KyqNNjV+(+FDCBX&@qH4-Tnh*{3A-8A zHJXF)Lo-~xFpn>~2?RZxvZtQph(!qdLB6q|0Dcm~;uX8vC&HcyPKA{awFwn3$Tgu_ z3U9GFeH#ktqpfGnjA(ph&CtciZmGYuC^d+K0+NbLCvu?)>!DY(=A>;zprq zFEyK4zHdQ!Ny+M==z&aE_iV;jWGrW7_C3DJ{Nc<(g1uH1l&ykE#7xU&>Igl3?gqO%&5!0U^EfyOu=6oW>E8v+7TFxn}XEZ7~xo0K3)GQ6T&EHa+bhhO62><4j#MO=7Cwdl`uV;yT z7cY%4aeK}8E>;ZpL+?#A?A5BROAV*REI-YwpZ+%X{$vCJWs%@sNMoN7>ef1e!ZogS z{}}DeR}B1xTAg~DR|1mz9RYm)>-W)8^tNN3|Ff3{!5b|gfFgq>dRx&@T+gl%@GaJjoAmVY8`iw@nOUH_N zL74M4?nFI-*SHrV@$oXEDxnLQ|D?fo+!x=xxH(-aidS^tWmBk-AIIC4#8ac_kDA=f z4SU4pz-~4bfJz8$uLYvG@%qp5;Hj1HFAv+?18;1=P&%^lHV}lOQKSs!N|?zWdngXz zIWcf1b#9$PD{W-FuyjHpFbOOZOfDS=Tug43Ll2W=IR$mI%^y?PXOswBh&tspulQ#F z5)6L?;2QFT9e@jGx$AAJX8vLT#`B;`f3=yKh2Ew;%%;aR@r?v5wfd#~iYU_E9WoV< zjY3nMNoLHI&ns(@+Wb)lc2G&LS%cLdbFTGfmK$HD2Xm`S@rVWFaEv{U(w4VuQQA4i z=F0KUvwGHCF5;|P05CP+C(Ww~I%hwSAr0SG1Ihw+>Mqyu`;m>Y>Uh8O%~k@Q5{mgq zZGD0jTF4|q>!u{A1rHyH(cS>{+T=hI zs}?+Du}WR{xB#m%{0Z+}dXqtE;?Aa}W65i#4Ux~%=uNh~)=mP)Nvwl8L^oIye4PZC zA!K?ZBU3v2p~{)B#^jUdMP7M<*vdzU4%~ijwZwu}+kL-dI%xy%?C^tQck15MfZgO~ zntO4l61Ss~x}ifXD$Tsh>!vrQt~DhgXU(9`unvD}S(tRGvOX1#Fu8{@`*)RC&khaTO>tJ+;w3&uCstX7%F{*u7 zA3pl5$m6;Im5yJymRU=AN4N5K4B>y=M&_=8f6jT2)lLZ7D`+)c5voyE6wsnxR_}OV z6okcIz)RqC$9m*^Xd|!IX-x{uX~w&|6XUSDOR7R_%E0Y~{43;)A$ZE;R1*;PXw=Idg9*S0B zx=3j@{gX^<-oq%G$w0*Ie2FHJ%PT3E+7SfYI&_=CXcQrDG*IzXg0Z#0W@%OZLYtP5 zQH8p(Cjz2Thw%8!ASTb*_f@yvAg2t_06nh1Ebb5?7^z;vx$ z=LZFmOnBuqaw17XO$P3!KU0X#&z`a?y?cZ9edAM;ql@p;!q-$@K$V3L?M;pzdN@9puJdK7%L&(5>UUb?xIvkN z<@0hea90Te*(3X;#cFv|qjx})94i4S<;`7O_P(rHdD$TOQO-hfop?fS-af&{P94UT zFx17%5vRHy@h1r(-jr!7rJ<;H?49z>GT5p+;(l5UL;>Cl)WO{ z@{@$&+Yev|{u8|96|SMfy`PhVz9Pqn6@(d6R;YThZy~jDsEfPb^L-x3tqV{}NiVHS zOT9n^CL|#I?G1DJ*4zD=X6tnuO6YfgERSI^d^+OLf^jX}y_az<^AnngN_dAH{>}rJ zX|*GQ({AyP@7@otyaG;F`AhdQ=R?IEl0#p%$|NY|zaVe?Z36oaZWWgockt1i!A!w6 zRS`&Jqg?g`5!;$GG;owz&5;x7L*sbo^m+|}Xu1wYJroB#O+My^q)jYzI zqxaVY(jKrZ-Ns}`Rm<}0=D-Lg?SPgo;hz@jroDON0&-Tip2HLOg`wBLsyOeU!}E>K zI~t-R1t3Jx+8~&p@SwOY3BETuH=C7X^9KIbMoa(O12Dx<^EX|$QY%I&!-7L1mNrT- zA|Akk>Rk_7bMn`r#1npt^1dirQzF`j2V8C&ISoAo9exp1wfT0NRj|1C)|wQRBWTh# z<}2;F<>pb1EXo~`n%iMxq(WVImcgHu@llT5E8a5MoKd?_m)cd3)h zq;*KDNRZIe1q$-uK-%)5fP%DLX zruUIv%)@19LHk%i6+g*rNe(AIb zXMZY$*(4iQ(3Lqb(sn}Q0b|71s4j-QtQqSP!1F3<9|uC%_(lprwCT52VLNxRmiv)3 zdJ5H+4oL7O3xQ!Sxd3;L<6z_Q*LzZ>IO&5%BQ0mSLi*p|P!vCdAhnIK%b7q<$J@yE z*!AmecL|_?+Yn;9;S1*DUTMrHCB+28N(R52-G zaMnf_0`+jp8N(#(lz>v8YPY1ik9&TK!81)j5jI(eXxI@aE#nGuzGZ!*`#Y-wLmaOQ z{>O*=-#7NNIS4qSCsN>wxf}Z~0whSfh9hq^Z0F<%%!uHv4m*(BMCP?xKv0}#=kl#I zAN>prXqK#W;G?rVKiLuW@YIN47Y1tJAzK>wA$q7d+fXJ`m1@r9vir4IeRlisWEYVWI*agk-0e9p6-Bi9@d!+l zMt*81SL?mJ1fr;fZN{9d&>71XmYV%l&$q103{87u@;oMaHdx02gu=Zar<1*KxBE!$ zb&u6Nt5rqDmupDUY4075XGr+c#+bZe80+`sh@Dho*+vyqW#kob{vP;ib0U9=d6zuu z0TYcYR9FqFpRLDj=^;Fn5HjfX!<)Yi1*Dr31IJTho@YQCz1AxRgr^94iPDQ}^Am%s z6KfjOAM!fhurZ*rIO+08?(o4L_J?U#aSV71+7GH=NfA1pqV*SJA;K(;*fzhSn5ZxH z$}B;qmR-oVr*TnQAFK}084=>EcI{I#drl%WIyR|fb&w~(n#8F!vYPf7x-tgKsnE)3 zuQcnUS!CSOOZF&;+R}oJDQ2I?p~u3jm#PZI?icYUQ%JjaK7AkxcwpzXg)uVGB-4u2 z2vZIZ=Al%5yWC|j3hw1=f#hL6O?pr~|8_4=Nw5`nabjH>k^RA%U@m*I$#TvYUCll- z<{v94RHnndtQrS$2U}54B*&ez#DtytTIV&{JvQEdF#zuG7=*7QII8-8qJzPEEo(eB zQ0qM}-Y+x=89h z7zRZSL+S>dP#c6-#b=sVy{uiYi$={6!CAD;RvE!#x0M3F%C^`oNM$nTTlq|PWBNsf z^1b3k)TX_M^FM}?mE%@Zd2lC+H;yIuPGGMGG@#9@$Ibz&e!x*f`_dUFjJ;dgx6U3r za>&T}jgvTAFv5Vr+Fi{w!3tWZ$6H~GILu4fNSKBccV!S<^-6G0=@BNb>g34fyA?iB z6XJyB-W>(~*8WqTn^?U{TE!hY%|48+zea3f!@cEY>g22@@+``f`U4fXMtu;^j2^J1 zv$^gAt5rOoj5+aF>$GQ$BTYywuO3#Y!@8UFonEoISKK#!!Sx`!Z#Ri7B6GygA~K^c`0^7) zsdhAOWNiH07^`z_xOGd)4h`1g_PIBCMzu%@RYzfh-xJR!QH~lk?DR>b`R3G`eCm0( zM*Og%g0B$wr>xpn442%uxLJEN6U@#E=@=@bBZsnW5VcKuQ>}2FA5gUh2xXBMLyR5( zjRmS+1OGK0`*<=#Kk#Bbz6#yuuJ1*sy*|XvIw8)z# z$rrxw(dfY06SvQNN;!yN*6Wi0$T*DrnoXp5I5ta^DHz?p-sYHyiqLc9w05F0lGzHT zKCA_u$SD}s;8Iq45@Q3)vuTuuXxVT~&?iYbUnZGMk6o%DM*OJE|4J0y7gLeAKAkk? z7oXM}(&)fs9MUczHt~&&e7R;tKqfd)z67yn!_g$&^!0_iA!-t&WZnK2HUv=}QfDJVAEHEtC*e zG>lBY`pNjkpt2BO12#b)L&j6Q0d1>?%VzhaiYL=X<*&>}egzHX&QF$jvKWQ(^h(9% zz$Sa6lTB(U^+70U;w0B-@a%ze+0by~Z7V`?y*%PV#3&58jYE$#-xwT+`7YRoIu#!r zU0Ygakxk4!Y^%P}?Hox@2vqD$IpwvaOL|`TI8}>vUKp!+B90*0`E}|HgD@>-Es1@R z3#;j-i=F%Nd}0~sZdDTvk^mLM#BRF6?ulgsx3di9ZY(?7U-v9|7tJeEg_!iaruix$ z(bnkw>t)uslLrTi_6tTQb}}LZ-iURqC9vntH@GK#Dz+p~(?IKaID`MG`es4X9%U0x z190iq(X(%<$@|$iIzMnFe#a4pHd*5hIwIymuX5bkbV+v9pWiEE!i9m83Dsv-9P
jPsu-7C2W<jHqHMjON}N*HPx-=g%Wf0{qnA@u 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 O1L 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 iz{~v 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 diff --git a/docs/reference/indices/index-mgmt.asciidoc b/docs/reference/indices/index-mgmt.asciidoc index 571f1c813fbfa..b89d2ec957af9 100644 --- a/docs/reference/indices/index-mgmt.asciidoc +++ b/docs/reference/indices/index-mgmt.asciidoc @@ -49,11 +49,11 @@ Badges indicate if an index is a <>, a Clicking a badge narrows the list to only indices of that type. You can also filter indices using the search bar. -You can drill down into each index to investigate the index +By clicking the index name, you can open an index details page to investigate the index <>, <>, and statistics. -From this view, you can also edit the index settings. +On this page, you can also edit the index settings. -To view and explore the documents within an index, click the compass icon image:compassicon.png[width=3%] next to the index name to open {kibana-ref}/discover.html[Discover]. +To view and explore the documents within an index, click the *Discover index* button to open {kibana-ref}/discover.html[Discover]. [role="screenshot"] image::images/index-mgmt/management_index_details.png[Index Management UI] From 787bca43deea01170f469ef4615ad697130aff27 Mon Sep 17 00:00:00 2001 From: Matteo Piergiovanni <134913285+piergm@users.noreply.github.com> Date: Thu, 28 Sep 2023 13:50:59 +0200 Subject: [PATCH 08/32] Mute SearchResponseTests#testSerialization (#100005) (#100015) --- .../org/elasticsearch/action/search/SearchResponseTests.java | 1 + 1 file changed, 1 insertion(+) diff --git a/server/src/test/java/org/elasticsearch/action/search/SearchResponseTests.java b/server/src/test/java/org/elasticsearch/action/search/SearchResponseTests.java index befea803e6fa0..9acb63db2cc90 100644 --- a/server/src/test/java/org/elasticsearch/action/search/SearchResponseTests.java +++ b/server/src/test/java/org/elasticsearch/action/search/SearchResponseTests.java @@ -590,6 +590,7 @@ public void testToXContent() throws IOException { } } + @AwaitsFix(bugUrl = "https://github.com/elastic/elasticsearch/issues/100005") public void testSerialization() throws IOException { SearchResponse searchResponse = createTestItem(false); SearchResponse deserialized = copyWriteable( From 2499f6c7b0a4d7d20fd223bfb220951779c881d2 Mon Sep 17 00:00:00 2001 From: David Roberts Date: Thu, 28 Sep 2023 13:53:21 +0100 Subject: [PATCH 09/32] [ML] Use TransportVersion for remote cluster age check (#100013) Previously the code was using MlConfigVersion for a remote cluster version check, which is inappropriate as the communications with the remote cluster are transport, not config. --- .../common/validation/SourceDestValidator.java | 3 --- .../xpack/core/ml/MlConfigVersion.java | 3 ++- .../xpack/core/ml/datafeed/DatafeedConfig.java | 7 ++++--- .../ml/action/TransportStartDatafeedAction.java | 16 ++++++++-------- .../TransportStartDatafeedActionTests.java | 13 +++++++------ 5 files changed, 21 insertions(+), 21 deletions(-) diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/common/validation/SourceDestValidator.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/common/validation/SourceDestValidator.java index 49bdf60ad0b18..9962f14ec3736 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/common/validation/SourceDestValidator.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/common/validation/SourceDestValidator.java @@ -73,9 +73,6 @@ public final class SourceDestValidator { public static final String REMOTE_CLUSTERS_TRANSPORT_TOO_OLD = "remote clusters are expected to run at least transport version [{0}] (reason: [{1}])," + " but the following clusters were too old: [{2}]"; - public static final String REMOTE_CLUSTERS_CONFIG_TOO_OLD = - "remote clusters are expected to run at least config version [{0}] (reason: [{1}])," - + " but the following clusters were too old: [{2}]"; public static final String PIPELINE_MISSING = "Pipeline with id [{0}] could not be found"; private final IndexNameExpressionResolver indexNameExpressionResolver; diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/MlConfigVersion.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/MlConfigVersion.java index 75370db1d766f..e631e3efe5cb6 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/MlConfigVersion.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/MlConfigVersion.java @@ -268,7 +268,8 @@ public static MlConfigVersion max(MlConfigVersion version1, MlConfigVersion vers return version1.id > version2.id ? version1 : version2; } - public static MlConfigVersion fromVersion(Version version) { + // Visible only for testing + static MlConfigVersion fromVersion(Version version) { if (version.equals(Version.V_8_10_0)) { return V_10; } diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/datafeed/DatafeedConfig.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/datafeed/DatafeedConfig.java index 7f5de886222ba..cacc4a6a33196 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/datafeed/DatafeedConfig.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/datafeed/DatafeedConfig.java @@ -9,6 +9,8 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.elasticsearch.ElasticsearchException; +import org.elasticsearch.TransportVersion; +import org.elasticsearch.TransportVersions; import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.action.support.IndicesOptions; import org.elasticsearch.cluster.SimpleDiffable; @@ -36,7 +38,6 @@ import org.elasticsearch.xcontent.XContentBuilder; import org.elasticsearch.xcontent.XContentParser; import org.elasticsearch.xpack.core.common.time.TimeUtils; -import org.elasticsearch.xpack.core.ml.MlConfigVersion; import org.elasticsearch.xpack.core.ml.datafeed.extractor.ExtractorUtils; import org.elasticsearch.xpack.core.ml.utils.ExceptionsHelper; import org.elasticsearch.xpack.core.ml.utils.MlStrings; @@ -86,7 +87,7 @@ */ public class DatafeedConfig implements SimpleDiffable, ToXContentObject { - private static final MlConfigVersion RUNTIME_MAPPINGS_INTRODUCED = MlConfigVersion.V_7_11_0; + private static final TransportVersion RUNTIME_MAPPINGS_INTRODUCED = TransportVersions.V_7_11_0; public static final int DEFAULT_SCROLL_SIZE = 1000; @@ -340,7 +341,7 @@ public Integer getScrollSize() { return scrollSize; } - public Optional> minRequiredConfigVersion() { + public Optional> minRequiredTransportVersion() { return runtimeMappings.isEmpty() ? Optional.empty() : Optional.of(Tuple.tuple(RUNTIME_MAPPINGS_INTRODUCED, SearchSourceBuilder.RUNTIME_MAPPINGS_FIELD.getPreferredName())); diff --git a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportStartDatafeedAction.java b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportStartDatafeedAction.java index 38b827b120bf6..475ca4ef2a7ce 100644 --- a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportStartDatafeedAction.java +++ b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportStartDatafeedAction.java @@ -10,6 +10,7 @@ import org.apache.logging.log4j.Logger; import org.elasticsearch.ElasticsearchStatusException; import org.elasticsearch.ResourceAlreadyExistsException; +import org.elasticsearch.TransportVersion; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.support.ActionFilters; import org.elasticsearch.action.support.master.TransportMasterNodeAction; @@ -45,7 +46,6 @@ import org.elasticsearch.xcontent.NamedXContentRegistry; import org.elasticsearch.xpack.core.XPackField; import org.elasticsearch.xpack.core.ml.MachineLearningField; -import org.elasticsearch.xpack.core.ml.MlConfigVersion; import org.elasticsearch.xpack.core.ml.MlTasks; import org.elasticsearch.xpack.core.ml.action.GetDatafeedRunningStateAction; import org.elasticsearch.xpack.core.ml.action.NodeAcknowledgedResponse; @@ -80,7 +80,7 @@ import java.util.function.Predicate; import java.util.stream.Collectors; -import static org.elasticsearch.xpack.core.common.validation.SourceDestValidator.REMOTE_CLUSTERS_CONFIG_TOO_OLD; +import static org.elasticsearch.xpack.core.common.validation.SourceDestValidator.REMOTE_CLUSTERS_TRANSPORT_TOO_OLD; /* This class extends from TransportMasterNodeAction for cluster state observing purposes. The stop datafeed api also redirect the elected master node. @@ -249,7 +249,7 @@ public void onFailure(Exception e) { checkRemoteConfigVersions( datafeedConfigHolder.get(), remoteAliases, - (cn) -> MlConfigVersion.fromVersion(remoteClusterService.getConnection(cn).getVersion()) + (cn) -> remoteClusterService.getConnection(cn).getTransportVersion() ); createDataExtractor(task, job, datafeedConfigHolder.get(), params, waitForTaskListener); } @@ -299,17 +299,17 @@ public void onFailure(Exception e) { static void checkRemoteConfigVersions( DatafeedConfig config, List remoteClusters, - Function configVersionSupplier + Function transportVersionSupplier ) { - Optional> minVersionAndReason = config.minRequiredConfigVersion(); + Optional> minVersionAndReason = config.minRequiredTransportVersion(); if (minVersionAndReason.isPresent() == false) { return; } final String reason = minVersionAndReason.get().v2(); - final MlConfigVersion minVersion = minVersionAndReason.get().v1(); + final TransportVersion minVersion = minVersionAndReason.get().v1(); List clustersTooOld = remoteClusters.stream() - .filter(cn -> configVersionSupplier.apply(cn).before(minVersion)) + .filter(cn -> transportVersionSupplier.apply(cn).before(minVersion)) .collect(Collectors.toList()); if (clustersTooOld.isEmpty()) { return; @@ -317,7 +317,7 @@ static void checkRemoteConfigVersions( throw ExceptionsHelper.badRequestException( Messages.getMessage( - REMOTE_CLUSTERS_CONFIG_TOO_OLD, + REMOTE_CLUSTERS_TRANSPORT_TOO_OLD, minVersion.toString(), reason, Strings.collectionToCommaDelimitedString(clustersTooOld) diff --git a/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/action/TransportStartDatafeedActionTests.java b/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/action/TransportStartDatafeedActionTests.java index 4a66a2836273e..a2d5003118536 100644 --- a/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/action/TransportStartDatafeedActionTests.java +++ b/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/action/TransportStartDatafeedActionTests.java @@ -8,13 +8,14 @@ package org.elasticsearch.xpack.ml.action; import org.elasticsearch.ElasticsearchStatusException; +import org.elasticsearch.TransportVersion; +import org.elasticsearch.TransportVersions; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.persistent.PersistentTasksCustomMetadata; import org.elasticsearch.search.SearchModule; import org.elasticsearch.tasks.TaskId; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.xcontent.NamedXContentRegistry; -import org.elasticsearch.xpack.core.ml.MlConfigVersion; import org.elasticsearch.xpack.core.ml.action.StartDatafeedAction; import org.elasticsearch.xpack.core.ml.datafeed.DatafeedConfig; import org.elasticsearch.xpack.core.ml.datafeed.DatafeedConfigTests; @@ -112,13 +113,13 @@ public void testNoDeprecationsLogged() { } public void testRemoteClusterVersionCheck() { - Map clusterVersions = Map.of( + Map clusterVersions = Map.of( "modern_cluster_1", - MlConfigVersion.CURRENT, + TransportVersion.current(), "modern_cluster_2", - MlConfigVersion.CURRENT, + TransportVersion.current(), "old_cluster_1", - MlConfigVersion.V_7_0_0 + TransportVersions.V_7_0_0 ); Map field = Map.of("runtime_field_foo", Map.of("type", "keyword", "script", "")); @@ -137,7 +138,7 @@ public void testRemoteClusterVersionCheck() { assertThat( ex.getMessage(), containsString( - "remote clusters are expected to run at least config version [7.11.0] (reason: [runtime_mappings]), " + "remote clusters are expected to run at least transport version [7110099] (reason: [runtime_mappings]), " + "but the following clusters were too old: [old_cluster_1]" ) ); From c6f4cb49143729128c05b104fa383e07b3884061 Mon Sep 17 00:00:00 2001 From: Mary Gouseti Date: Thu, 28 Sep 2023 15:55:39 +0300 Subject: [PATCH 10/32] Inactive replica of new primary shard is green (#99995) When we have a new initialisation of a primary shard we consider the this indicator `green` based on the idea that this are unexceptional events in the cluster's lifecycle. However, when we have a replica of this shard that is inactive we turn to `yellow`. With this PR, we change this behaviour to signal that if the primary is inactive and that is `green`, then an inactive replica of this shard is also `green`. Fixes #99951 --- docs/changelog/99995.yaml | 6 ++++++ ...ShardsAvailabilityHealthIndicatorService.java | 16 +++++++++++++--- ...sAvailabilityHealthIndicatorServiceTests.java | 13 +++++++++---- 3 files changed, 28 insertions(+), 7 deletions(-) create mode 100644 docs/changelog/99995.yaml diff --git a/docs/changelog/99995.yaml b/docs/changelog/99995.yaml new file mode 100644 index 0000000000000..d67cbdaec1f37 --- /dev/null +++ b/docs/changelog/99995.yaml @@ -0,0 +1,6 @@ +pr: 99995 +summary: When a primary is inactive but this is considered expected, the same applies for the replica of this shard. +area: Health +type: enhancement +issues: + - 99951 diff --git a/server/src/main/java/org/elasticsearch/cluster/routing/allocation/ShardsAvailabilityHealthIndicatorService.java b/server/src/main/java/org/elasticsearch/cluster/routing/allocation/ShardsAvailabilityHealthIndicatorService.java index e04e8a47349b6..9d41dd86d2ceb 100644 --- a/server/src/main/java/org/elasticsearch/cluster/routing/allocation/ShardsAvailabilityHealthIndicatorService.java +++ b/server/src/main/java/org/elasticsearch/cluster/routing/allocation/ShardsAvailabilityHealthIndicatorService.java @@ -402,7 +402,7 @@ private class ShardAllocationCounts { private final Map> diagnosisDefinitions = new HashMap<>(); public void increment(ShardRouting routing, ClusterState state, NodesShutdownMetadata shutdowns, boolean verbose) { - boolean isNew = isUnassignedDueToNewInitialization(routing); + boolean isNew = isUnassignedDueToNewInitialization(routing, state); boolean isRestarting = isUnassignedDueToTimelyRestart(routing, shutdowns); available &= routing.active() || isRestarting || isNew; if ((routing.active() || isRestarting || isNew) == false) { @@ -454,8 +454,14 @@ private static boolean isUnassignedDueToTimelyRestart(ShardRouting routing, Node return now - restartingAllocationDelayExpiration <= 0; } - private static boolean isUnassignedDueToNewInitialization(ShardRouting routing) { - return routing.primary() && routing.active() == false && getInactivePrimaryHealth(routing) == ClusterHealthStatus.YELLOW; + private static boolean isUnassignedDueToNewInitialization(ShardRouting routing, ClusterState state) { + if (routing.active()) { + return false; + } + // If the primary is inactive for unexceptional events in the cluster lifecycle, both the primary and the + // replica are considered new initializations. + ShardRouting primary = routing.primary() ? routing : state.routingTable().shardRoutingTable(routing.shardId()).primaryShard(); + return primary.active() == false && getInactivePrimaryHealth(primary) == ClusterHealthStatus.YELLOW; } /** @@ -815,6 +821,7 @@ public String getSymptom() { || primaries.unassigned_new > 0 || primaries.unassigned_restarting > 0 || replicas.unassigned > 0 + || replicas.unassigned_new > 0 || replicas.unassigned_restarting > 0 || primaries.initializing > 0 || replicas.initializing > 0) { @@ -822,6 +829,7 @@ public String getSymptom() { Stream.of( createMessage(primaries.unassigned, "unavailable primary shard", "unavailable primary shards"), createMessage(primaries.unassigned_new, "creating primary shard", "creating primary shards"), + createMessage(replicas.unassigned_new, "creating replica shard", "creating replica shards"), createMessage(primaries.unassigned_restarting, "restarting primary shard", "restarting primary shards"), createMessage(replicas.unassigned, "unavailable replica shard", "unavailable replica shards"), createMessage(primaries.initializing, "initializing primary shard", "initializing primary shards"), @@ -861,6 +869,8 @@ public HealthIndicatorDetails getDetails(boolean verbose) { replicas.unassigned, "initializing_replicas", replicas.initializing, + "creating_replicas", + replicas.unassigned_new, "restarting_replicas", replicas.unassigned_restarting, "started_replicas", diff --git a/server/src/test/java/org/elasticsearch/cluster/routing/allocation/ShardsAvailabilityHealthIndicatorServiceTests.java b/server/src/test/java/org/elasticsearch/cluster/routing/allocation/ShardsAvailabilityHealthIndicatorServiceTests.java index e6da007b085a3..708a3125590fd 100644 --- a/server/src/test/java/org/elasticsearch/cluster/routing/allocation/ShardsAvailabilityHealthIndicatorServiceTests.java +++ b/server/src/test/java/org/elasticsearch/cluster/routing/allocation/ShardsAvailabilityHealthIndicatorServiceTests.java @@ -533,8 +533,11 @@ public void testShouldBeYellowWhenRestartingReplicasReachedAllocationDelay() { ); } - public void testShouldBeGreenWhenThereAreInitializingPrimaries() { - var clusterState = createClusterStateWith(List.of(index("restarting-index", new ShardAllocation("node-0", CREATING))), List.of()); + public void testShouldBeGreenWhenThereAreInitializingPrimariesAndReplicas() { + var clusterState = createClusterStateWith( + List.of(index("restarting-index", new ShardAllocation("node-0", CREATING), new ShardAllocation("node-1", CREATING))), + List.of() + ); var service = createShardsAvailabilityIndicatorService(clusterState); assertThat( @@ -542,8 +545,8 @@ public void testShouldBeGreenWhenThereAreInitializingPrimaries() { equalTo( createExpectedResult( GREEN, - "This cluster has 1 creating primary shard.", - Map.of("creating_primaries", 1), + "This cluster has 1 creating primary shard, 1 creating replica shard.", + Map.of("creating_primaries", 1, "creating_replicas", 1), emptyList(), emptyList() ) @@ -1765,6 +1768,8 @@ private static Map addDefaults(Map override) { override.getOrDefault("started_primaries", 0), "unassigned_replicas", override.getOrDefault("unassigned_replicas", 0), + "creating_replicas", + override.getOrDefault("creating_replicas", 0), "initializing_replicas", override.getOrDefault("initializing_replicas", 0), "restarting_replicas", From e951d0efd02aa07cf8a1e9495b2fd045f01bd1ce Mon Sep 17 00:00:00 2001 From: Kostas Krikellas <131142368+kkrik-es@users.noreply.github.com> Date: Thu, 28 Sep 2023 17:19:00 +0300 Subject: [PATCH 11/32] [TEST] Test that aggregate_metric_double is unsupported in ESQL (#99952) * Test that aggregate_metric_double is unsupported in ESQL * Add from-clause test --- .../resources/rest-api-spec/test/40_tsdb.yml | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/x-pack/plugin/esql/qa/server/single-node/src/yamlRestTest/resources/rest-api-spec/test/40_tsdb.yml b/x-pack/plugin/esql/qa/server/single-node/src/yamlRestTest/resources/rest-api-spec/test/40_tsdb.yml index d72d09644a128..2e8c43379d690 100644 --- a/x-pack/plugin/esql/qa/server/single-node/src/yamlRestTest/resources/rest-api-spec/test/40_tsdb.yml +++ b/x-pack/plugin/esql/qa/server/single-node/src/yamlRestTest/resources/rest-api-spec/test/40_tsdb.yml @@ -60,6 +60,39 @@ setup: - '{"index": {}}' - '{"@timestamp": "2021-04-28T18:51:03.142Z", "metricset": "pod", "k8s": {"pod": {"name": "dog", "uid":"df3145b3-0563-4d3b-a0f7-897eb2876ea9", "ip": "10.10.55.3", "network": {"tx": 1434595272, "rx": 530605511}}}}' + - do: + indices.create: + index: test2 + body: + settings: + index: + mode: time_series + routing_path: [ dim ] + time_series: + start_time: 2021-04-28T00:00:00Z + end_time: 2021-04-29T00:00:00Z + mappings: + properties: + "@timestamp": + type: date + dim: + type: keyword + time_series_dimension: true + agg_metric: + type: aggregate_metric_double + metrics: + - max + default_metric: max + - do: + bulk: + refresh: true + index: test + body: + - '{"index": {}}' + - '{"@timestamp": "2021-04-28T18:50:04.467Z", "dim": "A", "agg_metric": {"max": 10}}' + - '{"index": {}}' + - '{"@timestamp": "2021-04-28T18:50:24.467Z", "dim": "B", "agg_metric": {"max": 20}}' + --- load everything: - do: @@ -109,3 +142,26 @@ filter on counter: esql.query: body: query: 'from test | where k8s.pod.network.tx == 1434577921' + +--- +from doc with aggregate_metric_double: + - do: + esql.query: + body: + query: 'from test2' + + - match: {columns.0.name: "@timestamp"} + - match: {columns.0.type: "date"} + - match: {columns.1.name: "agg_metric"} + - match: {columns.1.type: "unsupported"} + - match: {columns.2.name: "dim"} + - match: {columns.2.type: "keyword"} + - length: {values: 0} + +--- +stats on aggregate_metric_double: + - do: + catch: /Cannot use field \[agg_metric\] with unsupported type \[aggregate_metric_double\]/ + esql.query: + body: + query: 'FROM test2 | STATS max(agg_metric) BY dim ' From ddd95d568a1057402c6ef9d479667ad1c1736c83 Mon Sep 17 00:00:00 2001 From: David Roberts Date: Thu, 28 Sep 2023 16:16:05 +0100 Subject: [PATCH 12/32] [Transform] Hide the fromVersion method of TransformConfigVersion (#100008) This method wasn't used anywhere, but we don't want to allow anyone to use it in the future. --- .../xpack/core/transform/TransformConfigVersion.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/transform/TransformConfigVersion.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/transform/TransformConfigVersion.java index acc10008cd40f..3d6a1aef8477a 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/transform/TransformConfigVersion.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/transform/TransformConfigVersion.java @@ -302,7 +302,8 @@ public static TransformConfigVersion max(TransformConfigVersion version1, Transf return version1.id > version2.id ? version1 : version2; } - public static TransformConfigVersion fromVersion(Version version) { + // Visible only for testing + static TransformConfigVersion fromVersion(Version version) { if (version.equals(Version.V_8_10_0)) { return V_10; } From 517a5425c569c5b0c2b5c0be6b6a3810c21f63ff Mon Sep 17 00:00:00 2001 From: Armin Braun Date: Thu, 28 Sep 2023 17:55:00 +0200 Subject: [PATCH 13/32] Deduplicate fields in FieldCaps intra-cluster messages (#100022) We have a stable equality check for individual fields capabilities. Even in the dynamic mapping case where mappings vary across indices, we tend to have a lot of overlap across mappings. By dedplicating the individual field capabilities instances when serializing we can reduce the size of the field caps messages massively in many cases. A repeated field takes up only an extra ~4 bytes in the transport message which compared to tens of MB in the general case. Even if deduplication were to never apply this change should reduce response sizes though as it only adds about ~4 bytes in overhead for a never deduplicated field but saves the redundant double writing of field names we used to do (we wrote them both in the map key and in the value) and it seems safe to assume that almost all field names are longer than 4 bytes. --- .../org/elasticsearch/TransportVersions.java | 2 + .../FieldCapabilitiesIndexResponse.java | 67 ++++++++++++++++++- 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/server/src/main/java/org/elasticsearch/TransportVersions.java b/server/src/main/java/org/elasticsearch/TransportVersions.java index 926cc4801dddc..5f120134acb04 100644 --- a/server/src/main/java/org/elasticsearch/TransportVersions.java +++ b/server/src/main/java/org/elasticsearch/TransportVersions.java @@ -145,6 +145,8 @@ static TransportVersion def(int id) { public static final TransportVersion WAIT_FOR_CLUSTER_STATE_IN_RECOVERY_ADDED = def(8_502_00_0); public static final TransportVersion RECOVERY_COMMIT_TOO_NEW_EXCEPTION_ADDED = def(8_503_00_0); public static final TransportVersion NODE_INFO_COMPONENT_VERSIONS_ADDED = def(8_504_00_0); + + public static final TransportVersion COMPACT_FIELD_CAPS_ADDED = def(8_505_00_0); /* * STOP! READ THIS FIRST! No, really, * ____ _____ ___ ____ _ ____ _____ _ ____ _____ _ _ ___ ____ _____ ___ ____ ____ _____ _ diff --git a/server/src/main/java/org/elasticsearch/action/fieldcaps/FieldCapabilitiesIndexResponse.java b/server/src/main/java/org/elasticsearch/action/fieldcaps/FieldCapabilitiesIndexResponse.java index b1f844ed66215..e9e3a05169afc 100644 --- a/server/src/main/java/org/elasticsearch/action/fieldcaps/FieldCapabilitiesIndexResponse.java +++ b/server/src/main/java/org/elasticsearch/action/fieldcaps/FieldCapabilitiesIndexResponse.java @@ -13,11 +13,13 @@ import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.io.stream.Writeable; +import org.elasticsearch.common.util.Maps; import org.elasticsearch.core.Nullable; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -67,6 +69,8 @@ public void writeTo(StreamOutput out) throws IOException { } } + private record CompressedGroup(String[] indices, String mappingHash, int[] fields) {} + static List readList(StreamInput input) throws IOException { if (input.getTransportVersion().before(MAPPING_HASH_VERSION)) { return input.readCollectionAsList(FieldCapabilitiesIndexResponse::new); @@ -77,6 +81,37 @@ static List readList(StreamInput input) throws I responses.add(new FieldCapabilitiesIndexResponse(input)); } final int groups = input.readVInt(); + if (input.getTransportVersion().onOrAfter(TransportVersions.COMPACT_FIELD_CAPS_ADDED)) { + collectCompressedResponses(input, groups, responses); + } else { + collectResponsesLegacyFormat(input, groups, responses); + } + return responses; + } + + private static void collectCompressedResponses(StreamInput input, int groups, ArrayList responses) + throws IOException { + final CompressedGroup[] compressedGroups = new CompressedGroup[groups]; + for (int i = 0; i < groups; i++) { + final String[] indices = input.readStringArray(); + final String mappingHash = input.readString(); + compressedGroups[i] = new CompressedGroup(indices, mappingHash, input.readIntArray()); + } + final IndexFieldCapabilities[] ifcLookup = input.readArray(IndexFieldCapabilities::readFrom, IndexFieldCapabilities[]::new); + for (CompressedGroup compressedGroup : compressedGroups) { + final Map ifc = Maps.newMapWithExpectedSize(compressedGroup.fields.length); + for (int i : compressedGroup.fields) { + var val = ifcLookup[i]; + ifc.put(val.name(), val); + } + for (String index : compressedGroup.indices) { + responses.add(new FieldCapabilitiesIndexResponse(index, compressedGroup.mappingHash, ifc, true)); + } + } + } + + private static void collectResponsesLegacyFormat(StreamInput input, int groups, ArrayList responses) + throws IOException { for (int i = 0; i < groups; i++) { final List indices = input.readStringCollectionAsList(); final String mappingHash = input.readString(); @@ -85,7 +120,6 @@ static List readList(StreamInput input) throws I responses.add(new FieldCapabilitiesIndexResponse(index, mappingHash, ifc, true)); } } - return responses; } static void writeList(StreamOutput output, List responses) throws IOException { @@ -103,7 +137,19 @@ static void writeList(StreamOutput output, List ungroupedResponses.add(r); } } + output.writeCollection(ungroupedResponses); + if (output.getTransportVersion().onOrAfter(TransportVersions.COMPACT_FIELD_CAPS_ADDED)) { + writeCompressedResponses(output, groupedResponsesMap); + } else { + writeResponsesLegacyFormat(output, groupedResponsesMap); + } + } + + private static void writeResponsesLegacyFormat( + StreamOutput output, + Map> groupedResponsesMap + ) throws IOException { output.writeCollection(groupedResponsesMap.values(), (o, fieldCapabilitiesIndexResponses) -> { o.writeCollection(fieldCapabilitiesIndexResponses, (oo, r) -> oo.writeString(r.indexName)); var first = fieldCapabilitiesIndexResponses.get(0); @@ -112,6 +158,25 @@ static void writeList(StreamOutput output, List }); } + private static void writeCompressedResponses(StreamOutput output, Map> groupedResponsesMap) + throws IOException { + final Map fieldDedupMap = new LinkedHashMap<>(); + output.writeCollection(groupedResponsesMap.values(), (o, fieldCapabilitiesIndexResponses) -> { + o.writeCollection(fieldCapabilitiesIndexResponses, (oo, r) -> oo.writeString(r.indexName)); + var first = fieldCapabilitiesIndexResponses.get(0); + o.writeString(first.indexMappingHash); + o.writeVInt(first.responseMap.size()); + for (IndexFieldCapabilities ifc : first.responseMap.values()) { + Integer offset = fieldDedupMap.size(); + final Integer found = fieldDedupMap.putIfAbsent(ifc, offset); + o.writeInt(found == null ? offset : found); + } + }); + // this is a linked hash map so the key-set is written in insertion order, so we can just write it out in order and then read it + // back as an array of FieldCapabilitiesIndexResponse in #collectCompressedResponses to use as a lookup + output.writeCollection(fieldDedupMap.keySet()); + } + /** * Get the index name */ From 246e8324c8531e3821d3498e15477160874fe99a Mon Sep 17 00:00:00 2001 From: David Kyle Date: Thu, 28 Sep 2023 17:34:26 +0100 Subject: [PATCH 14/32] [ML] Handle malformed inference result (#100023) Improved logging and best attempt at handling what is expected to be a rare edge case --- .../process/PyTorchResultProcessor.java | 38 +++++++++++++++---- .../process/PyTorchResultProcessorTests.java | 17 +++++++++ 2 files changed, 47 insertions(+), 8 deletions(-) diff --git a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/inference/pytorch/process/PyTorchResultProcessor.java b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/inference/pytorch/process/PyTorchResultProcessor.java index 6b92a9349c4ea..4b925464d985b 100644 --- a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/inference/pytorch/process/PyTorchResultProcessor.java +++ b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/inference/pytorch/process/PyTorchResultProcessor.java @@ -107,17 +107,19 @@ public void process(PyTorchProcess process) { if (result.inferenceResult() != null) { processInferenceResult(result); - } - ThreadSettings threadSettings = result.threadSettings(); - if (threadSettings != null) { - threadSettingsConsumer.accept(threadSettings); + } else if (result.threadSettings() != null) { + threadSettingsConsumer.accept(result.threadSettings()); processThreadSettings(result); - } - if (result.ackResult() != null) { + } else if (result.ackResult() != null) { processAcknowledgement(result); - } - if (result.errorResult() != null) { + } else if (result.errorResult() != null) { processErrorResult(result); + } else { + // will should only get here if the native process + // has produced a partially valid result, one that + // is accepted by the parser but does not have any + // content + handleUnknownResultType(result); } } } catch (Exception e) { @@ -208,6 +210,26 @@ void processErrorResult(PyTorchResult result) { } } + void handleUnknownResultType(PyTorchResult result) { + if (result.requestId() != null) { + PendingResult pendingResult = pendingResults.remove(result.requestId()); + if (pendingResult == null) { + logger.error(() -> format("[%s] no pending result listener for unknown result type [%s]", modelId, result)); + } else { + String msg = format("[%s] pending result listener cannot handle unknown result type [%s]", modelId, result); + logger.error(msg); + var errorResult = new ErrorResult(msg); + pendingResult.listener.onResponse(new PyTorchResult(result.requestId(), null, null, null, null, null, errorResult)); + } + } else { + // Cannot look up the listener without a request id + // all that can be done in this case is log a message. + // The result parser requires a request id so this + // code should not be hit. + logger.error(() -> format("[%s] cannot process unknown result type [%s]", modelId, result)); + } + } + public synchronized ResultStats getResultStats() { long currentMs = currentTimeMsSupplier.getAsLong(); long currentPeriodStartTimeMs = startTime + Intervals.alignToFloor(currentMs - startTime, REPORTING_PERIOD_MS); diff --git a/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/inference/pytorch/process/PyTorchResultProcessorTests.java b/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/inference/pytorch/process/PyTorchResultProcessorTests.java index e172f4ffb528c..860da3140f4fe 100644 --- a/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/inference/pytorch/process/PyTorchResultProcessorTests.java +++ b/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/inference/pytorch/process/PyTorchResultProcessorTests.java @@ -153,6 +153,23 @@ public void testPendingRequestAreCalledAtShutdown() { } } + public void testsHandleUnknownResult() { + var processor = new PyTorchResultProcessor("deployment-foo", settings -> {}); + var listener = new AssertingResultListener( + r -> assertThat( + r.errorResult().error(), + containsString("[deployment-foo] pending result listener cannot handle unknown result type") + ) + ); + + processor.registerRequest("no-result-content", listener); + + processor.process( + mockNativeProcess(List.of(new PyTorchResult("no-result-content", null, null, null, null, null, null)).iterator()) + ); + assertTrue(listener.hasResponse); + } + private static class AssertingResultListener implements ActionListener { boolean hasResponse; final Consumer responseAsserter; From 0e219307f2d63bcbfc4357c75bf7c4b510801e21 Mon Sep 17 00:00:00 2001 From: Nik Everett Date: Thu, 28 Sep 2023 13:17:08 -0400 Subject: [PATCH 15/32] ESQL: Track blocks (#100025) This tracks blocks from topn and a few other places. We're going to try and track blocks all the places. --- .../compute/operator/TopNBenchmark.java | 2 + .../org/elasticsearch/core/Releasables.java | 2 +- .../common/util/BytesRefArray.java | 9 +- .../common/util/MockBigArrays.java | 8 +- .../indices/CrankyCircuitBreakerService.java | 15 +- .../compute/data/BooleanArrayBlock.java | 1 + .../compute/data/BooleanBlockBuilder.java | 16 +- .../compute/data/BooleanVectorBuilder.java | 13 +- .../data/BooleanVectorFixedBuilder.java | 8 + .../compute/data/BytesRefArrayBlock.java | 3 +- .../compute/data/BytesRefArrayVector.java | 2 +- .../compute/data/BytesRefBlockBuilder.java | 29 +++- .../compute/data/BytesRefVectorBuilder.java | 30 +++- .../compute/data/DoubleArrayBlock.java | 1 + .../compute/data/DoubleBlockBuilder.java | 16 +- .../compute/data/DoubleVectorBuilder.java | 13 +- .../data/DoubleVectorFixedBuilder.java | 8 + .../compute/data/IntArrayBlock.java | 1 + .../compute/data/IntBlockBuilder.java | 16 +- .../compute/data/IntVectorBuilder.java | 13 +- .../compute/data/IntVectorFixedBuilder.java | 8 + .../compute/data/LongArrayBlock.java | 1 + .../compute/data/LongBlockBuilder.java | 16 +- .../compute/data/LongVectorBuilder.java | 13 +- .../compute/data/LongVectorFixedBuilder.java | 8 + .../topn/ResultBuilderForBoolean.java | 10 +- .../topn/ResultBuilderForBytesRef.java | 10 +- .../operator/topn/ResultBuilderForDouble.java | 10 +- .../operator/topn/ResultBuilderForInt.java | 10 +- .../operator/topn/ResultBuilderForLong.java | 10 +- .../compute/data/AbstractBlockBuilder.java | 34 ++++ .../compute/data/AbstractVectorBuilder.java | 41 ++++- .../org/elasticsearch/compute/data/Block.java | 6 +- .../compute/data/BlockFactory.java | 4 +- .../compute/data/BlockUtils.java | 2 +- .../compute/data/ConstantNullBlock.java | 14 ++ .../elasticsearch/compute/data/DocBlock.java | 17 +- .../compute/data/ElementType.java | 25 ++- .../elasticsearch/compute/data/Vector.java | 6 +- .../compute/data/X-ArrayBlock.java.st | 3 +- .../compute/data/X-ArrayVector.java.st | 2 +- .../compute/data/X-BlockBuilder.java.st | 55 +++++- .../compute/data/X-VectorBuilder.java.st | 48 +++++- .../compute/data/X-VectorFixedBuilder.java.st | 8 + .../compute/operator/Driver.java | 8 +- .../exchange/ExchangeSinkOperator.java | 4 - .../compute/operator/topn/ResultBuilder.java | 26 ++- .../operator/topn/ResultBuilderForDoc.java | 16 +- .../operator/topn/ResultBuilderForNull.java | 13 +- .../compute/operator/topn/TopNOperator.java | 67 ++++++-- .../operator/topn/X-ResultBuilder.java.st | 10 +- .../elasticsearch/compute/OperatorTests.java | 2 +- .../AggregatorFunctionTestCase.java | 8 +- .../CountAggregatorFunctionTests.java | 5 +- ...istinctBooleanAggregatorFunctionTests.java | 3 +- ...ooleanGroupingAggregatorFunctionTests.java | 3 +- ...stinctBytesRefAggregatorFunctionTests.java | 3 +- ...tesRefGroupingAggregatorFunctionTests.java | 3 +- ...DistinctDoubleAggregatorFunctionTests.java | 3 +- ...DoubleGroupingAggregatorFunctionTests.java | 3 +- ...untDistinctIntAggregatorFunctionTests.java | 2 +- ...nctIntGroupingAggregatorFunctionTests.java | 3 +- ...ntDistinctLongAggregatorFunctionTests.java | 4 +- ...ctLongGroupingAggregatorFunctionTests.java | 4 +- .../CountGroupingAggregatorFunctionTests.java | 4 +- .../GroupingAggregatorFunctionTestCase.java | 28 +-- .../MaxDoubleAggregatorFunctionTests.java | 3 +- ...DoubleGroupingAggregatorFunctionTests.java | 3 +- .../MaxIntAggregatorFunctionTests.java | 3 +- ...MaxIntGroupingAggregatorFunctionTests.java | 3 +- .../MaxLongAggregatorFunctionTests.java | 5 +- ...axLongGroupingAggregatorFunctionTests.java | 8 +- ...eviationDoubleAggregatorFunctionTests.java | 3 +- ...DoubleGroupingAggregatorFunctionTests.java | 3 +- ...teDeviationIntAggregatorFunctionTests.java | 3 +- ...ionIntGroupingAggregatorFunctionTests.java | 3 +- ...eDeviationLongAggregatorFunctionTests.java | 5 +- ...onLongGroupingAggregatorFunctionTests.java | 5 +- .../MinDoubleAggregatorFunctionTests.java | 3 +- ...DoubleGroupingAggregatorFunctionTests.java | 3 +- .../MinIntAggregatorFunctionTests.java | 3 +- ...MinIntGroupingAggregatorFunctionTests.java | 3 +- .../MinLongAggregatorFunctionTests.java | 5 +- ...inLongGroupingAggregatorFunctionTests.java | 8 +- ...rcentileDoubleAggregatorFunctionTests.java | 3 +- ...DoubleGroupingAggregatorFunctionTests.java | 3 +- .../PercentileIntAggregatorFunctionTests.java | 3 +- ...ileIntGroupingAggregatorFunctionTests.java | 3 +- ...PercentileLongAggregatorFunctionTests.java | 5 +- ...leLongGroupingAggregatorFunctionTests.java | 4 +- .../SumDoubleAggregatorFunctionTests.java | 3 +- ...DoubleGroupingAggregatorFunctionTests.java | 3 +- .../SumIntAggregatorFunctionTests.java | 2 +- ...SumIntGroupingAggregatorFunctionTests.java | 3 +- .../SumLongAggregatorFunctionTests.java | 6 +- ...umLongGroupingAggregatorFunctionTests.java | 4 +- .../compute/data/BlockBuilderTests.java | 146 +++++++++++++--- .../compute/data/BlockFactoryTests.java | 28 ++- .../compute/data/BlockTestUtils.java | 1 + .../data/BytesRefBlockEqualityTests.java | 37 ++-- .../compute/data/DocVectorTests.java | 57 ++++--- .../data/DoubleBlockEqualityTests.java | 37 ++-- .../compute/data/IntBlockEqualityTests.java | 37 ++-- .../compute/data/LongBlockEqualityTests.java | 39 +++-- .../compute/data/TestBlockBuilder.java | 25 +++ .../compute/data/VectorBuilderTests.java | 153 +++++++++++++++++ .../compute/data/VectorFixedBuilderTests.java | 39 +++-- .../ValuesSourceReaderOperatorTests.java | 30 +++- .../operator/AggregationOperatorTests.java | 5 +- .../compute/operator/AnyOperatorTestCase.java | 2 +- .../operator/CannedSourceOperator.java | 25 ++- .../operator/ColumnExtractOperatorTests.java | 3 +- .../compute/operator/EvalOperatorTests.java | 5 +- .../compute/operator/FilterOperatorTests.java | 5 +- .../operator/ForkingOperatorTestCase.java | 12 +- .../HashAggregationOperatorTests.java | 8 +- .../compute/operator/LimitOperatorTests.java | 5 +- .../operator/MvExpandOperatorTests.java | 3 +- .../compute/operator/OperatorTestCase.java | 50 ++++-- .../operator/ProjectOperatorTests.java | 2 +- .../SequenceLongBlockSourceOperator.java | 18 +- .../operator/StringExtractOperatorTests.java | 3 +- .../operator/TupleBlockSourceOperator.java | 12 +- .../compute/operator/topn/ExtractorTests.java | 17 +- .../operator/topn/TopNOperatorTests.java | 160 +++++++++++++++--- .../rest-api-spec/test/50_index_patterns.yml | 59 +++---- .../metadata-ignoreCsvTests.csv-spec | 3 +- .../xpack/esql/action/EsqlActionIT.java | 145 ++++++++-------- .../xpack/esql/action/EsqlDisruptionIT.java | 4 + 129 files changed, 1592 insertions(+), 486 deletions(-) create mode 100644 x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/VectorBuilderTests.java diff --git a/benchmarks/src/main/java/org/elasticsearch/benchmark/compute/operator/TopNBenchmark.java b/benchmarks/src/main/java/org/elasticsearch/benchmark/compute/operator/TopNBenchmark.java index 9ef4eef2a6924..84f7cec47b737 100644 --- a/benchmarks/src/main/java/org/elasticsearch/benchmark/compute/operator/TopNBenchmark.java +++ b/benchmarks/src/main/java/org/elasticsearch/benchmark/compute/operator/TopNBenchmark.java @@ -13,6 +13,7 @@ import org.elasticsearch.common.settings.ClusterSettings; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.BlockFactory; import org.elasticsearch.compute.data.BooleanBlock; import org.elasticsearch.compute.data.BytesRefBlock; import org.elasticsearch.compute.data.DoubleBlock; @@ -107,6 +108,7 @@ private static Operator operator(String data, int topCount) { ClusterSettings.createBuiltInClusterSettings() ); return new TopNOperator( + BlockFactory.getNonBreakingInstance(), breakerService.getBreaker(CircuitBreaker.REQUEST), topCount, elementTypes, diff --git a/libs/core/src/main/java/org/elasticsearch/core/Releasables.java b/libs/core/src/main/java/org/elasticsearch/core/Releasables.java index b8d1a9a542779..c2b48c4706573 100644 --- a/libs/core/src/main/java/org/elasticsearch/core/Releasables.java +++ b/libs/core/src/main/java/org/elasticsearch/core/Releasables.java @@ -89,7 +89,7 @@ private static void close(boolean success, Releasable... releasables) { * // the resources will be released when reaching here * */ - public static Releasable wrap(final Iterable releasables) { + public static Releasable wrap(final Iterable releasables) { return new Releasable() { @Override public void close() { diff --git a/server/src/main/java/org/elasticsearch/common/util/BytesRefArray.java b/server/src/main/java/org/elasticsearch/common/util/BytesRefArray.java index de061c7f314d6..91dbfc30123fe 100644 --- a/server/src/main/java/org/elasticsearch/common/util/BytesRefArray.java +++ b/server/src/main/java/org/elasticsearch/common/util/BytesRefArray.java @@ -160,7 +160,14 @@ public void writeTo(StreamOutput out) throws IOException { @Override public long ramBytesUsed() { - return BASE_RAM_BYTES_USED + startOffsets.ramBytesUsed() + bytes.ramBytesUsed(); + return BASE_RAM_BYTES_USED + bigArraysRamBytesUsed(); + } + + /** + * Memory used by the {@link BigArrays} portion of this {@link BytesRefArray}. + */ + public long bigArraysRamBytesUsed() { + return startOffsets.ramBytesUsed() + bytes.ramBytesUsed(); } } diff --git a/test/framework/src/main/java/org/elasticsearch/common/util/MockBigArrays.java b/test/framework/src/main/java/org/elasticsearch/common/util/MockBigArrays.java index 02e8cfd7f16fc..0a0592b5a01f2 100644 --- a/test/framework/src/main/java/org/elasticsearch/common/util/MockBigArrays.java +++ b/test/framework/src/main/java/org/elasticsearch/common/util/MockBigArrays.java @@ -678,6 +678,9 @@ public void addEstimateBytesAndMaybeBreak(long bytes, String label) throws Circu while (true) { long old = used.get(); long total = old + bytes; + if (total < 0) { + throw new AssertionError("total must be >= 0 but was [" + total + "]"); + } if (total > max.getBytes()) { throw new CircuitBreakingException(ERROR_MESSAGE, bytes, max.getBytes(), Durability.TRANSIENT); } @@ -689,7 +692,10 @@ public void addEstimateBytesAndMaybeBreak(long bytes, String label) throws Circu @Override public void addWithoutBreaking(long bytes) { - used.addAndGet(bytes); + long total = used.addAndGet(bytes); + if (total < 0) { + throw new AssertionError("total must be >= 0 but was [" + total + "]"); + } } @Override diff --git a/test/framework/src/main/java/org/elasticsearch/indices/CrankyCircuitBreakerService.java b/test/framework/src/main/java/org/elasticsearch/indices/CrankyCircuitBreakerService.java index 15ffa52569d00..bd5f974a5f800 100644 --- a/test/framework/src/main/java/org/elasticsearch/indices/CrankyCircuitBreakerService.java +++ b/test/framework/src/main/java/org/elasticsearch/indices/CrankyCircuitBreakerService.java @@ -15,6 +15,8 @@ import org.elasticsearch.indices.breaker.CircuitBreakerStats; import org.elasticsearch.test.ESTestCase; +import java.util.concurrent.atomic.AtomicLong; + /** * {@link CircuitBreakerService} that fails one twentieth of the time when you * add bytes. This is useful to make sure code responds sensibly to circuit @@ -27,31 +29,32 @@ public class CrankyCircuitBreakerService extends CircuitBreakerService { public static final String ERROR_MESSAGE = "cranky breaker"; private final CircuitBreaker breaker = new CircuitBreaker() { - @Override - public void circuitBreak(String fieldName, long bytesNeeded) { + private final AtomicLong used = new AtomicLong(); - } + @Override + public void circuitBreak(String fieldName, long bytesNeeded) {} @Override public void addEstimateBytesAndMaybeBreak(long bytes, String label) throws CircuitBreakingException { if (ESTestCase.random().nextInt(20) == 0) { throw new CircuitBreakingException(ERROR_MESSAGE, Durability.PERMANENT); } + used.addAndGet(bytes); } @Override public void addWithoutBreaking(long bytes) { - + used.addAndGet(bytes); } @Override public long getUsed() { - return 0; + return used.get(); } @Override public long getLimit() { - return 0; + return Long.MAX_VALUE; } @Override diff --git a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/BooleanArrayBlock.java b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/BooleanArrayBlock.java index d8a5e471aaf84..b6e36e698355b 100644 --- a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/BooleanArrayBlock.java +++ b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/BooleanArrayBlock.java @@ -75,6 +75,7 @@ public BooleanBlock expand() { public static long ramBytesEstimated(boolean[] values, int[] firstValueIndexes, BitSet nullsMask) { return BASE_RAM_BYTES_USED + RamUsageEstimator.sizeOf(values) + BlockRamUsageEstimator.sizeOf(firstValueIndexes) + BlockRamUsageEstimator.sizeOfBitSet(nullsMask) + RamUsageEstimator.shallowSizeOfInstance(MvOrdering.class); + // TODO mvordering is shared } @Override diff --git a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/BooleanBlockBuilder.java b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/BooleanBlockBuilder.java index a7d397fcfb98e..98b9fdb948bc0 100644 --- a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/BooleanBlockBuilder.java +++ b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/BooleanBlockBuilder.java @@ -7,6 +7,8 @@ package org.elasticsearch.compute.data; +import org.apache.lucene.util.RamUsageEstimator; + import java.util.Arrays; /** @@ -20,7 +22,7 @@ final class BooleanBlockBuilder extends AbstractBlockBuilder implements BooleanB BooleanBlockBuilder(int estimatedSize, BlockFactory blockFactory) { super(blockFactory); int initialSize = Math.max(estimatedSize, 2); - adjustBreaker(initialSize); + adjustBreaker(RamUsageEstimator.NUM_BYTES_ARRAY_HEADER + initialSize * elementSize()); values = new boolean[initialSize]; } @@ -192,8 +194,16 @@ public BooleanBlock build() { block = new BooleanArrayBlock(values, positionCount, firstValueIndexes, nullsMask, mvOrdering, blockFactory); } } - // update the breaker with the actual bytes used. - blockFactory.adjustBreaker(block.ramBytesUsed() - estimatedBytes, true); + /* + * Update the breaker with the actual bytes used. + * We pass false below even though we've used the bytes. That's weird, + * but if we break here we will throw away the used memory, letting + * it be deallocated. The exception will bubble up and the builder will + * still technically be open, meaning the calling code should close it + * which will return all used memory to the breaker. + */ + blockFactory.adjustBreaker(block.ramBytesUsed() - estimatedBytes, false); + built(); return block; } } diff --git a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/BooleanVectorBuilder.java b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/BooleanVectorBuilder.java index 45c74ee6e06d4..3792e39275f82 100644 --- a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/BooleanVectorBuilder.java +++ b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/BooleanVectorBuilder.java @@ -49,6 +49,7 @@ protected void growValuesArray(int newSize) { @Override public BooleanVector build() { + finish(); BooleanVector vector; if (valueCount == 1) { vector = new ConstantBooleanVector(values[0], 1, blockFactory); @@ -58,8 +59,16 @@ public BooleanVector build() { } vector = new BooleanArrayVector(values, valueCount, blockFactory); } - // update the breaker with the actual bytes used. - blockFactory.adjustBreaker(vector.ramBytesUsed() - estimatedBytes, true); + /* + * Update the breaker with the actual bytes used. + * We pass false below even though we've used the bytes. That's weird, + * but if we break here we will throw away the used memory, letting + * it be deallocated. The exception will bubble up and the builder will + * still technically be open, meaning the calling code should close it + * which will return all used memory to the breaker. + */ + blockFactory.adjustBreaker(vector.ramBytesUsed() - estimatedBytes, false); + built(); return vector; } } diff --git a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/BooleanVectorFixedBuilder.java b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/BooleanVectorFixedBuilder.java index 30146d4e55c02..1428a1a221fa3 100644 --- a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/BooleanVectorFixedBuilder.java +++ b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/BooleanVectorFixedBuilder.java @@ -58,4 +58,12 @@ public BooleanVector build() { } return new BooleanArrayVector(values, values.length, blockFactory); } + + @Override + public void close() { + if (nextIndex >= 0) { + // If nextIndex < 0 we've already built the vector + blockFactory.adjustBreaker(-ramBytesUsed(values.length), false); + } + } } diff --git a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/BytesRefArrayBlock.java b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/BytesRefArrayBlock.java index e4ee70cd27a47..db5b5d3fcf804 100644 --- a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/BytesRefArrayBlock.java +++ b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/BytesRefArrayBlock.java @@ -77,6 +77,7 @@ public BytesRefBlock expand() { public static long ramBytesEstimated(BytesRefArray values, int[] firstValueIndexes, BitSet nullsMask) { return BASE_RAM_BYTES_USED + RamUsageEstimator.sizeOf(values) + BlockRamUsageEstimator.sizeOf(firstValueIndexes) + BlockRamUsageEstimator.sizeOfBitSet(nullsMask) + RamUsageEstimator.shallowSizeOfInstance(MvOrdering.class); + // TODO mvordering is shared } @Override @@ -115,7 +116,7 @@ public void close() { throw new IllegalStateException("can't release already released block [" + this + "]"); } released = true; - blockFactory.adjustBreaker(-(ramBytesUsed() - values.ramBytesUsed()), true); + blockFactory.adjustBreaker(-ramBytesUsed() + values.bigArraysRamBytesUsed(), true); Releasables.closeExpectNoException(values); } } diff --git a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/BytesRefArrayVector.java b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/BytesRefArrayVector.java index c8f5276a99db5..1692bfc59358a 100644 --- a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/BytesRefArrayVector.java +++ b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/BytesRefArrayVector.java @@ -85,7 +85,7 @@ public String toString() { @Override public void close() { - blockFactory.adjustBreaker(-BASE_RAM_BYTES_USED, true); + blockFactory.adjustBreaker(-ramBytesUsed() + values.bigArraysRamBytesUsed(), true); Releasables.closeExpectNoException(values); } } diff --git a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/BytesRefBlockBuilder.java b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/BytesRefBlockBuilder.java index 23c18d2a9ca6e..a60b26667eb79 100644 --- a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/BytesRefBlockBuilder.java +++ b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/BytesRefBlockBuilder.java @@ -193,19 +193,42 @@ public BytesRefBlockBuilder mvOrdering(Block.MvOrdering mvOrdering) { public BytesRefBlock build() { finish(); BytesRefBlock block; + assert estimatedBytes == 0 || firstValueIndexes != null; if (hasNonNullValue && positionCount == 1 && valueCount == 1) { block = new ConstantBytesRefVector(BytesRef.deepCopyOf(values.get(0, new BytesRef())), 1, blockFactory).asBlock(); + /* + * Update the breaker with the actual bytes used. + * We pass false below even though we've used the bytes. That's weird, + * but if we break here we will throw away the used memory, letting + * it be deallocated. The exception will bubble up and the builder will + * still technically be open, meaning the calling code should close it + * which will return all used memory to the breaker. + */ + blockFactory.adjustBreaker(block.ramBytesUsed() - estimatedBytes, false); Releasables.closeExpectNoException(values); } else { - estimatedBytes += values.ramBytesUsed(); if (isDense() && singleValued()) { block = new BytesRefArrayVector(values, positionCount, blockFactory).asBlock(); } else { block = new BytesRefArrayBlock(values, positionCount, firstValueIndexes, nullsMask, mvOrdering, blockFactory); } + /* + * Update the breaker with the actual bytes used. + * We pass false below even though we've used the bytes. That's weird, + * but if we break here we will throw away the used memory, letting + * it be deallocated. The exception will bubble up and the builder will + * still technically be open, meaning the calling code should close it + * which will return all used memory to the breaker. + */ + blockFactory.adjustBreaker(block.ramBytesUsed() - estimatedBytes - values.bigArraysRamBytesUsed(), false); } - // update the breaker with the actual bytes used. - blockFactory.adjustBreaker(block.ramBytesUsed() - estimatedBytes, true); + values = null; + built(); return block; } + + @Override + public void extraClose() { + Releasables.closeExpectNoException(values); + } } diff --git a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/BytesRefVectorBuilder.java b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/BytesRefVectorBuilder.java index f37ffb2a7e28a..5ea9a2b7d0184 100644 --- a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/BytesRefVectorBuilder.java +++ b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/BytesRefVectorBuilder.java @@ -54,16 +54,40 @@ protected void growValuesArray(int newSize) { @Override public BytesRefVector build() { + finish(); BytesRefVector vector; + assert estimatedBytes == 0; if (valueCount == 1) { vector = new ConstantBytesRefVector(BytesRef.deepCopyOf(values.get(0, new BytesRef())), 1, blockFactory); + /* + * Update the breaker with the actual bytes used. + * We pass false below even though we've used the bytes. That's weird, + * but if we break here we will throw away the used memory, letting + * it be deallocated. The exception will bubble up and the builder will + * still technically be open, meaning the calling code should close it + * which will return all used memory to the breaker. + */ + blockFactory.adjustBreaker(vector.ramBytesUsed(), false); Releasables.closeExpectNoException(values); } else { - estimatedBytes = values.ramBytesUsed(); vector = new BytesRefArrayVector(values, valueCount, blockFactory); + /* + * Update the breaker with the actual bytes used. + * We pass false below even though we've used the bytes. That's weird, + * but if we break here we will throw away the used memory, letting + * it be deallocated. The exception will bubble up and the builder will + * still technically be open, meaning the calling code should close it + * which will return all used memory to the breaker. + */ + blockFactory.adjustBreaker(vector.ramBytesUsed() - values.bigArraysRamBytesUsed(), false); } - // update the breaker with the actual bytes used. - blockFactory.adjustBreaker(vector.ramBytesUsed() - estimatedBytes, true); + values = null; + built(); return vector; } + + @Override + public void extraClose() { + Releasables.closeExpectNoException(values); + } } diff --git a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/DoubleArrayBlock.java b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/DoubleArrayBlock.java index b0de974a85c24..675952a8d6a85 100644 --- a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/DoubleArrayBlock.java +++ b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/DoubleArrayBlock.java @@ -75,6 +75,7 @@ public DoubleBlock expand() { public static long ramBytesEstimated(double[] values, int[] firstValueIndexes, BitSet nullsMask) { return BASE_RAM_BYTES_USED + RamUsageEstimator.sizeOf(values) + BlockRamUsageEstimator.sizeOf(firstValueIndexes) + BlockRamUsageEstimator.sizeOfBitSet(nullsMask) + RamUsageEstimator.shallowSizeOfInstance(MvOrdering.class); + // TODO mvordering is shared } @Override diff --git a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/DoubleBlockBuilder.java b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/DoubleBlockBuilder.java index a97f58f3924b1..dca8fe2d0d2e6 100644 --- a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/DoubleBlockBuilder.java +++ b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/DoubleBlockBuilder.java @@ -7,6 +7,8 @@ package org.elasticsearch.compute.data; +import org.apache.lucene.util.RamUsageEstimator; + import java.util.Arrays; /** @@ -20,7 +22,7 @@ final class DoubleBlockBuilder extends AbstractBlockBuilder implements DoubleBlo DoubleBlockBuilder(int estimatedSize, BlockFactory blockFactory) { super(blockFactory); int initialSize = Math.max(estimatedSize, 2); - adjustBreaker(initialSize); + adjustBreaker(RamUsageEstimator.NUM_BYTES_ARRAY_HEADER + initialSize * elementSize()); values = new double[initialSize]; } @@ -192,8 +194,16 @@ public DoubleBlock build() { block = new DoubleArrayBlock(values, positionCount, firstValueIndexes, nullsMask, mvOrdering, blockFactory); } } - // update the breaker with the actual bytes used. - blockFactory.adjustBreaker(block.ramBytesUsed() - estimatedBytes, true); + /* + * Update the breaker with the actual bytes used. + * We pass false below even though we've used the bytes. That's weird, + * but if we break here we will throw away the used memory, letting + * it be deallocated. The exception will bubble up and the builder will + * still technically be open, meaning the calling code should close it + * which will return all used memory to the breaker. + */ + blockFactory.adjustBreaker(block.ramBytesUsed() - estimatedBytes, false); + built(); return block; } } diff --git a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/DoubleVectorBuilder.java b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/DoubleVectorBuilder.java index f92ec67aec012..12fa06a944fbc 100644 --- a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/DoubleVectorBuilder.java +++ b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/DoubleVectorBuilder.java @@ -49,6 +49,7 @@ protected void growValuesArray(int newSize) { @Override public DoubleVector build() { + finish(); DoubleVector vector; if (valueCount == 1) { vector = new ConstantDoubleVector(values[0], 1, blockFactory); @@ -58,8 +59,16 @@ public DoubleVector build() { } vector = new DoubleArrayVector(values, valueCount, blockFactory); } - // update the breaker with the actual bytes used. - blockFactory.adjustBreaker(vector.ramBytesUsed() - estimatedBytes, true); + /* + * Update the breaker with the actual bytes used. + * We pass false below even though we've used the bytes. That's weird, + * but if we break here we will throw away the used memory, letting + * it be deallocated. The exception will bubble up and the builder will + * still technically be open, meaning the calling code should close it + * which will return all used memory to the breaker. + */ + blockFactory.adjustBreaker(vector.ramBytesUsed() - estimatedBytes, false); + built(); return vector; } } diff --git a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/DoubleVectorFixedBuilder.java b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/DoubleVectorFixedBuilder.java index 83992ed71b720..b636d9eb19756 100644 --- a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/DoubleVectorFixedBuilder.java +++ b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/DoubleVectorFixedBuilder.java @@ -58,4 +58,12 @@ public DoubleVector build() { } return new DoubleArrayVector(values, values.length, blockFactory); } + + @Override + public void close() { + if (nextIndex >= 0) { + // If nextIndex < 0 we've already built the vector + blockFactory.adjustBreaker(-ramBytesUsed(values.length), false); + } + } } diff --git a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/IntArrayBlock.java b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/IntArrayBlock.java index 7a345941df019..4170009b89ab2 100644 --- a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/IntArrayBlock.java +++ b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/IntArrayBlock.java @@ -75,6 +75,7 @@ public IntBlock expand() { public static long ramBytesEstimated(int[] values, int[] firstValueIndexes, BitSet nullsMask) { return BASE_RAM_BYTES_USED + RamUsageEstimator.sizeOf(values) + BlockRamUsageEstimator.sizeOf(firstValueIndexes) + BlockRamUsageEstimator.sizeOfBitSet(nullsMask) + RamUsageEstimator.shallowSizeOfInstance(MvOrdering.class); + // TODO mvordering is shared } @Override diff --git a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/IntBlockBuilder.java b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/IntBlockBuilder.java index 53d379d715c9b..ba96f85e73197 100644 --- a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/IntBlockBuilder.java +++ b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/IntBlockBuilder.java @@ -7,6 +7,8 @@ package org.elasticsearch.compute.data; +import org.apache.lucene.util.RamUsageEstimator; + import java.util.Arrays; /** @@ -20,7 +22,7 @@ final class IntBlockBuilder extends AbstractBlockBuilder implements IntBlock.Bui IntBlockBuilder(int estimatedSize, BlockFactory blockFactory) { super(blockFactory); int initialSize = Math.max(estimatedSize, 2); - adjustBreaker(initialSize); + adjustBreaker(RamUsageEstimator.NUM_BYTES_ARRAY_HEADER + initialSize * elementSize()); values = new int[initialSize]; } @@ -192,8 +194,16 @@ public IntBlock build() { block = new IntArrayBlock(values, positionCount, firstValueIndexes, nullsMask, mvOrdering, blockFactory); } } - // update the breaker with the actual bytes used. - blockFactory.adjustBreaker(block.ramBytesUsed() - estimatedBytes, true); + /* + * Update the breaker with the actual bytes used. + * We pass false below even though we've used the bytes. That's weird, + * but if we break here we will throw away the used memory, letting + * it be deallocated. The exception will bubble up and the builder will + * still technically be open, meaning the calling code should close it + * which will return all used memory to the breaker. + */ + blockFactory.adjustBreaker(block.ramBytesUsed() - estimatedBytes, false); + built(); return block; } } diff --git a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/IntVectorBuilder.java b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/IntVectorBuilder.java index 0533d5463a4e7..155adfec02b9f 100644 --- a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/IntVectorBuilder.java +++ b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/IntVectorBuilder.java @@ -49,6 +49,7 @@ protected void growValuesArray(int newSize) { @Override public IntVector build() { + finish(); IntVector vector; if (valueCount == 1) { vector = new ConstantIntVector(values[0], 1, blockFactory); @@ -58,8 +59,16 @@ public IntVector build() { } vector = new IntArrayVector(values, valueCount, blockFactory); } - // update the breaker with the actual bytes used. - blockFactory.adjustBreaker(vector.ramBytesUsed() - estimatedBytes, true); + /* + * Update the breaker with the actual bytes used. + * We pass false below even though we've used the bytes. That's weird, + * but if we break here we will throw away the used memory, letting + * it be deallocated. The exception will bubble up and the builder will + * still technically be open, meaning the calling code should close it + * which will return all used memory to the breaker. + */ + blockFactory.adjustBreaker(vector.ramBytesUsed() - estimatedBytes, false); + built(); return vector; } } diff --git a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/IntVectorFixedBuilder.java b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/IntVectorFixedBuilder.java index 19303b4024869..03a15fb10a800 100644 --- a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/IntVectorFixedBuilder.java +++ b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/IntVectorFixedBuilder.java @@ -58,4 +58,12 @@ public IntVector build() { } return new IntArrayVector(values, values.length, blockFactory); } + + @Override + public void close() { + if (nextIndex >= 0) { + // If nextIndex < 0 we've already built the vector + blockFactory.adjustBreaker(-ramBytesUsed(values.length), false); + } + } } diff --git a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/LongArrayBlock.java b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/LongArrayBlock.java index 21c6b445cd37d..778ec4294180c 100644 --- a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/LongArrayBlock.java +++ b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/LongArrayBlock.java @@ -75,6 +75,7 @@ public LongBlock expand() { public static long ramBytesEstimated(long[] values, int[] firstValueIndexes, BitSet nullsMask) { return BASE_RAM_BYTES_USED + RamUsageEstimator.sizeOf(values) + BlockRamUsageEstimator.sizeOf(firstValueIndexes) + BlockRamUsageEstimator.sizeOfBitSet(nullsMask) + RamUsageEstimator.shallowSizeOfInstance(MvOrdering.class); + // TODO mvordering is shared } @Override diff --git a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/LongBlockBuilder.java b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/LongBlockBuilder.java index a378b382ce31e..09d858e7c9b03 100644 --- a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/LongBlockBuilder.java +++ b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/LongBlockBuilder.java @@ -7,6 +7,8 @@ package org.elasticsearch.compute.data; +import org.apache.lucene.util.RamUsageEstimator; + import java.util.Arrays; /** @@ -20,7 +22,7 @@ final class LongBlockBuilder extends AbstractBlockBuilder implements LongBlock.B LongBlockBuilder(int estimatedSize, BlockFactory blockFactory) { super(blockFactory); int initialSize = Math.max(estimatedSize, 2); - adjustBreaker(initialSize); + adjustBreaker(RamUsageEstimator.NUM_BYTES_ARRAY_HEADER + initialSize * elementSize()); values = new long[initialSize]; } @@ -192,8 +194,16 @@ public LongBlock build() { block = new LongArrayBlock(values, positionCount, firstValueIndexes, nullsMask, mvOrdering, blockFactory); } } - // update the breaker with the actual bytes used. - blockFactory.adjustBreaker(block.ramBytesUsed() - estimatedBytes, true); + /* + * Update the breaker with the actual bytes used. + * We pass false below even though we've used the bytes. That's weird, + * but if we break here we will throw away the used memory, letting + * it be deallocated. The exception will bubble up and the builder will + * still technically be open, meaning the calling code should close it + * which will return all used memory to the breaker. + */ + blockFactory.adjustBreaker(block.ramBytesUsed() - estimatedBytes, false); + built(); return block; } } diff --git a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/LongVectorBuilder.java b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/LongVectorBuilder.java index 6b2e9f1de7d51..3b8bbf4219d00 100644 --- a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/LongVectorBuilder.java +++ b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/LongVectorBuilder.java @@ -49,6 +49,7 @@ protected void growValuesArray(int newSize) { @Override public LongVector build() { + finish(); LongVector vector; if (valueCount == 1) { vector = new ConstantLongVector(values[0], 1, blockFactory); @@ -58,8 +59,16 @@ public LongVector build() { } vector = new LongArrayVector(values, valueCount, blockFactory); } - // update the breaker with the actual bytes used. - blockFactory.adjustBreaker(vector.ramBytesUsed() - estimatedBytes, true); + /* + * Update the breaker with the actual bytes used. + * We pass false below even though we've used the bytes. That's weird, + * but if we break here we will throw away the used memory, letting + * it be deallocated. The exception will bubble up and the builder will + * still technically be open, meaning the calling code should close it + * which will return all used memory to the breaker. + */ + blockFactory.adjustBreaker(vector.ramBytesUsed() - estimatedBytes, false); + built(); return vector; } } diff --git a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/LongVectorFixedBuilder.java b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/LongVectorFixedBuilder.java index 5414b7669f588..0960d607d9c0d 100644 --- a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/LongVectorFixedBuilder.java +++ b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/data/LongVectorFixedBuilder.java @@ -58,4 +58,12 @@ public LongVector build() { } return new LongArrayVector(values, values.length, blockFactory); } + + @Override + public void close() { + if (nextIndex >= 0) { + // If nextIndex < 0 we've already built the vector + blockFactory.adjustBreaker(-ramBytesUsed(values.length), false); + } + } } diff --git a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/operator/topn/ResultBuilderForBoolean.java b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/operator/topn/ResultBuilderForBoolean.java index 50cef0417dd45..3d568adc2b5ea 100644 --- a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/operator/topn/ResultBuilderForBoolean.java +++ b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/operator/topn/ResultBuilderForBoolean.java @@ -8,6 +8,7 @@ package org.elasticsearch.compute.operator.topn; import org.apache.lucene.util.BytesRef; +import org.elasticsearch.compute.data.BlockFactory; import org.elasticsearch.compute.data.BooleanBlock; class ResultBuilderForBoolean implements ResultBuilder { @@ -20,10 +21,10 @@ class ResultBuilderForBoolean implements ResultBuilder { */ private boolean key; - ResultBuilderForBoolean(TopNEncoder encoder, boolean inKey, int initialSize) { + ResultBuilderForBoolean(BlockFactory blockFactory, TopNEncoder encoder, boolean inKey, int initialSize) { assert encoder == TopNEncoder.DEFAULT_UNSORTABLE : encoder.toString(); this.inKey = inKey; - this.builder = BooleanBlock.newBlockBuilder(initialSize); + this.builder = BooleanBlock.newBlockBuilder(initialSize, blockFactory); } @Override @@ -63,4 +64,9 @@ public BooleanBlock build() { public String toString() { return "ResultBuilderForBoolean[inKey=" + inKey + "]"; } + + @Override + public void close() { + builder.close(); + } } diff --git a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/operator/topn/ResultBuilderForBytesRef.java b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/operator/topn/ResultBuilderForBytesRef.java index 55f324c931b67..e37f82f3363a9 100644 --- a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/operator/topn/ResultBuilderForBytesRef.java +++ b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/operator/topn/ResultBuilderForBytesRef.java @@ -8,6 +8,7 @@ package org.elasticsearch.compute.operator.topn; import org.apache.lucene.util.BytesRef; +import org.elasticsearch.compute.data.BlockFactory; import org.elasticsearch.compute.data.BytesRefBlock; class ResultBuilderForBytesRef implements ResultBuilder { @@ -24,10 +25,10 @@ class ResultBuilderForBytesRef implements ResultBuilder { */ private BytesRef key; - ResultBuilderForBytesRef(TopNEncoder encoder, boolean inKey, int initialSize) { + ResultBuilderForBytesRef(BlockFactory blockFactory, TopNEncoder encoder, boolean inKey, int initialSize) { this.encoder = encoder; this.inKey = inKey; - this.builder = BytesRefBlock.newBlockBuilder(initialSize); + this.builder = BytesRefBlock.newBlockBuilder(initialSize, blockFactory); } @Override @@ -67,4 +68,9 @@ public BytesRefBlock build() { public String toString() { return "ResultBuilderForBytesRef[inKey=" + inKey + "]"; } + + @Override + public void close() { + builder.close(); + } } diff --git a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/operator/topn/ResultBuilderForDouble.java b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/operator/topn/ResultBuilderForDouble.java index ed4a9b45d90dc..77c976c6e0085 100644 --- a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/operator/topn/ResultBuilderForDouble.java +++ b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/operator/topn/ResultBuilderForDouble.java @@ -8,6 +8,7 @@ package org.elasticsearch.compute.operator.topn; import org.apache.lucene.util.BytesRef; +import org.elasticsearch.compute.data.BlockFactory; import org.elasticsearch.compute.data.DoubleBlock; class ResultBuilderForDouble implements ResultBuilder { @@ -20,10 +21,10 @@ class ResultBuilderForDouble implements ResultBuilder { */ private double key; - ResultBuilderForDouble(TopNEncoder encoder, boolean inKey, int initialSize) { + ResultBuilderForDouble(BlockFactory blockFactory, TopNEncoder encoder, boolean inKey, int initialSize) { assert encoder == TopNEncoder.DEFAULT_UNSORTABLE : encoder.toString(); this.inKey = inKey; - this.builder = DoubleBlock.newBlockBuilder(initialSize); + this.builder = DoubleBlock.newBlockBuilder(initialSize, blockFactory); } @Override @@ -63,4 +64,9 @@ public DoubleBlock build() { public String toString() { return "ResultBuilderForDouble[inKey=" + inKey + "]"; } + + @Override + public void close() { + builder.close(); + } } diff --git a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/operator/topn/ResultBuilderForInt.java b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/operator/topn/ResultBuilderForInt.java index 2bcfc81107445..389ed3bc2e3c3 100644 --- a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/operator/topn/ResultBuilderForInt.java +++ b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/operator/topn/ResultBuilderForInt.java @@ -8,6 +8,7 @@ package org.elasticsearch.compute.operator.topn; import org.apache.lucene.util.BytesRef; +import org.elasticsearch.compute.data.BlockFactory; import org.elasticsearch.compute.data.IntBlock; class ResultBuilderForInt implements ResultBuilder { @@ -20,10 +21,10 @@ class ResultBuilderForInt implements ResultBuilder { */ private int key; - ResultBuilderForInt(TopNEncoder encoder, boolean inKey, int initialSize) { + ResultBuilderForInt(BlockFactory blockFactory, TopNEncoder encoder, boolean inKey, int initialSize) { assert encoder == TopNEncoder.DEFAULT_UNSORTABLE : encoder.toString(); this.inKey = inKey; - this.builder = IntBlock.newBlockBuilder(initialSize); + this.builder = IntBlock.newBlockBuilder(initialSize, blockFactory); } @Override @@ -63,4 +64,9 @@ public IntBlock build() { public String toString() { return "ResultBuilderForInt[inKey=" + inKey + "]"; } + + @Override + public void close() { + builder.close(); + } } diff --git a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/operator/topn/ResultBuilderForLong.java b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/operator/topn/ResultBuilderForLong.java index 3ada85bf9d5c9..63ee9d35c59e5 100644 --- a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/operator/topn/ResultBuilderForLong.java +++ b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/operator/topn/ResultBuilderForLong.java @@ -8,6 +8,7 @@ package org.elasticsearch.compute.operator.topn; import org.apache.lucene.util.BytesRef; +import org.elasticsearch.compute.data.BlockFactory; import org.elasticsearch.compute.data.LongBlock; class ResultBuilderForLong implements ResultBuilder { @@ -20,10 +21,10 @@ class ResultBuilderForLong implements ResultBuilder { */ private long key; - ResultBuilderForLong(TopNEncoder encoder, boolean inKey, int initialSize) { + ResultBuilderForLong(BlockFactory blockFactory, TopNEncoder encoder, boolean inKey, int initialSize) { assert encoder == TopNEncoder.DEFAULT_UNSORTABLE : encoder.toString(); this.inKey = inKey; - this.builder = LongBlock.newBlockBuilder(initialSize); + this.builder = LongBlock.newBlockBuilder(initialSize, blockFactory); } @Override @@ -63,4 +64,9 @@ public LongBlock build() { public String toString() { return "ResultBuilderForLong[inKey=" + inKey + "]"; } + + @Override + public void close() { + builder.close(); + } } diff --git a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/AbstractBlockBuilder.java b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/AbstractBlockBuilder.java index a6ad5d1299543..3d06eba398513 100644 --- a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/AbstractBlockBuilder.java +++ b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/AbstractBlockBuilder.java @@ -33,6 +33,8 @@ abstract class AbstractBlockBuilder implements Block.Builder { /** The number of bytes currently estimated with the breaker. */ protected long estimatedBytes; + private boolean closed = false; + protected AbstractBlockBuilder(BlockFactory blockFactory) { this.blockFactory = blockFactory; } @@ -101,7 +103,14 @@ protected final void updatePosition() { } } + /** + * Called during implementations of {@link Block.Builder#build} as a first step + * to check if the block is still open and to finish the last position. + */ protected final void finish() { + if (closed) { + throw new IllegalStateException("already closed"); + } if (positionEntryIsOpen) { endPositionEntry(); } @@ -110,6 +119,16 @@ protected final void finish() { } } + /** + * Called during implementations of {@link Block.Builder#build} as a last step + * to mark the Builder as closed and make sure that further closes don't double + * free memory. + */ + protected final void built() { + closed = true; + estimatedBytes = 0; + } + protected abstract void growValuesArray(int newSize); /** The number of bytes used to represent each value element. */ @@ -125,6 +144,20 @@ protected final void ensureCapacity() { growValuesArray(newSize); } + @Override + public final void close() { + if (closed == false) { + closed = true; + adjustBreaker(-estimatedBytes); + extraClose(); + } + } + + /** + * Called when first {@link #close() closed}. + */ + protected void extraClose() {} + static int calculateNewArraySize(int currentSize) { // trivially, grows array by 50% return currentSize + (currentSize >> 1); @@ -133,6 +166,7 @@ static int calculateNewArraySize(int currentSize) { protected void adjustBreaker(long deltaBytes) { blockFactory.adjustBreaker(deltaBytes, false); estimatedBytes += deltaBytes; + assert estimatedBytes >= 0; } private void setFirstValue(int position, int value) { diff --git a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/AbstractVectorBuilder.java b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/AbstractVectorBuilder.java index 49ce276074735..274e88cd8d8b6 100644 --- a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/AbstractVectorBuilder.java +++ b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/AbstractVectorBuilder.java @@ -7,9 +7,14 @@ package org.elasticsearch.compute.data; -abstract class AbstractVectorBuilder { +abstract class AbstractVectorBuilder implements Vector.Builder { protected int valueCount; + /** + * Has this builder been closed already? + */ + private boolean closed = false; + protected final BlockFactory blockFactory; /** The number of bytes currently estimated with the breaker. */ @@ -46,4 +51,38 @@ protected void adjustBreaker(long deltaBytes) { blockFactory.adjustBreaker(deltaBytes, false); estimatedBytes += deltaBytes; } + + /** + * Called during implementations of {@link Block.Builder#build} as a first step + * to check if the block is still open and to finish the last position. + */ + protected final void finish() { + if (closed) { + throw new IllegalStateException("already closed"); + } + } + + /** + * Called during implementations of {@link Block.Builder#build} as a last step + * to mark the Builder as closed and make sure that further closes don't double + * free memory. + */ + protected final void built() { + closed = true; + estimatedBytes = 0; + } + + @Override + public final void close() { + if (closed == false) { + closed = true; + adjustBreaker(-estimatedBytes); + extraClose(); + } + } + + /** + * Called when first {@link #close() closed}. + */ + protected void extraClose() {} } diff --git a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/Block.java b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/Block.java index 5b10a3a510de0..ca0721ebbe4f8 100644 --- a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/Block.java +++ b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/Block.java @@ -133,7 +133,11 @@ static Block constantNullBlock(int positions, BlockFactory blockFactory) { return blockFactory.newConstantNullBlock(positions); } - interface Builder { + /** + * Builds {@link Block}s. Typically, you use one of it's direct supinterfaces like {@link IntBlock.Builder}. + * This is {@link Releasable} and should be released after building the block or if building the block fails. + */ + interface Builder extends Releasable { /** * Appends a null value to the block. diff --git a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/BlockFactory.java b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/BlockFactory.java index 2afea228a4a78..63de604c49b18 100644 --- a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/BlockFactory.java +++ b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/BlockFactory.java @@ -305,7 +305,7 @@ public BytesRefBlock newBytesRefArrayBlock( MvOrdering mvOrdering ) { var b = new BytesRefArrayBlock(values, positionCount, firstValueIndexes, nulls, mvOrdering, this); - adjustBreaker(b.ramBytesUsed() - values.ramBytesUsed(), true); + adjustBreaker(b.ramBytesUsed() - values.bigArraysRamBytesUsed(), true); return b; } @@ -315,7 +315,7 @@ public BytesRefVector.Builder newBytesRefVectorBuilder(int estimatedSize) { public BytesRefVector newBytesRefArrayVector(BytesRefArray values, int positionCount) { var b = new BytesRefArrayVector(values, positionCount, this); - adjustBreaker(b.ramBytesUsed() - values.ramBytesUsed(), true); + adjustBreaker(b.ramBytesUsed() - values.bigArraysRamBytesUsed(), true); return b; } diff --git a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/BlockUtils.java b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/BlockUtils.java index 2ebbb771b5df1..0d09f7bb480e0 100644 --- a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/BlockUtils.java +++ b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/BlockUtils.java @@ -210,7 +210,7 @@ public static Object toJavaObject(Block block, int position) { private static Object valueAtOffset(Block block, int offset) { return switch (block.elementType()) { case BOOLEAN -> ((BooleanBlock) block).getBoolean(offset); - case BYTES_REF -> ((BytesRefBlock) block).getBytesRef(offset, new BytesRef()); + case BYTES_REF -> BytesRef.deepCopyOf(((BytesRefBlock) block).getBytesRef(offset, new BytesRef())); case DOUBLE -> ((DoubleBlock) block).getDouble(offset); case INT -> ((IntBlock) block).getInt(offset); case LONG -> ((LongBlock) block).getLong(offset); diff --git a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/ConstantNullBlock.java b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/ConstantNullBlock.java index 2da9cfeba09f0..01994af1cfc96 100644 --- a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/ConstantNullBlock.java +++ b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/ConstantNullBlock.java @@ -136,6 +136,11 @@ public void close() { static class Builder implements Block.Builder { private int positionCount; + /** + * Has this builder been closed already? + */ + private boolean closed = false; + @Override public Builder appendNull() { positionCount++; @@ -174,7 +179,16 @@ public Block.Builder mvOrdering(MvOrdering mvOrdering) { @Override public Block build() { + if (closed) { + throw new IllegalStateException("already closed"); + } + close(); return new ConstantNullBlock(positionCount); } + + @Override + public void close() { + closed = true; + } } } diff --git a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/DocBlock.java b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/DocBlock.java index b21a956980f6a..6bcf913ce6240 100644 --- a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/DocBlock.java +++ b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/DocBlock.java @@ -82,8 +82,8 @@ public void close() { /** * A builder the for {@link DocBlock}. */ - public static Builder newBlockBuilder(int estimatedSize) { - return new Builder(estimatedSize); + public static Builder newBlockBuilder(int estimatedSize, BlockFactory blockFactory) { + return new Builder(estimatedSize, blockFactory); } public static class Builder implements Block.Builder { @@ -91,10 +91,10 @@ public static class Builder implements Block.Builder { private final IntVector.Builder segments; private final IntVector.Builder docs; - private Builder(int estimatedSize) { - shards = IntVector.newVectorBuilder(estimatedSize); - segments = IntVector.newVectorBuilder(estimatedSize); - docs = IntVector.newVectorBuilder(estimatedSize); + private Builder(int estimatedSize, BlockFactory blockFactory) { + shards = IntVector.newVectorBuilder(estimatedSize, blockFactory); + segments = IntVector.newVectorBuilder(estimatedSize, blockFactory); + docs = IntVector.newVectorBuilder(estimatedSize, blockFactory); } public Builder appendShard(int shard) { @@ -153,5 +153,10 @@ public DocBlock build() { // Pass null for singleSegmentNonDecreasing so we calculate it when we first need it. return new DocVector(shards.build(), segments.build(), docs.build(), null).asBlock(); } + + @Override + public void close() { + Releasables.closeExpectNoException(shards, segments, docs); + } } } diff --git a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/ElementType.java b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/ElementType.java index 0c85d433018e0..4467766a9e0ef 100644 --- a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/ElementType.java +++ b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/ElementType.java @@ -9,8 +9,6 @@ import org.apache.lucene.util.BytesRef; -import java.util.function.IntFunction; - /** * The type of elements in {@link Block} and {@link Vector} */ @@ -22,7 +20,7 @@ public enum ElementType { /** * Blocks containing only null values. */ - NULL(estimatedSize -> new ConstantNullBlock.Builder()), + NULL((estimatedSize, blockFactory) -> new ConstantNullBlock.Builder()), BYTES_REF(BytesRefBlock::newBlockBuilder), @@ -34,19 +32,32 @@ public enum ElementType { /** * Intermediate blocks which don't support retrieving elements. */ - UNKNOWN(estimatedSize -> { throw new UnsupportedOperationException("can't build null blocks"); }); + UNKNOWN((estimatedSize, blockFactory) -> { throw new UnsupportedOperationException("can't build null blocks"); }); + + interface BuilderSupplier { + Block.Builder newBlockBuilder(int estimatedSize, BlockFactory blockFactory); + } - private final IntFunction builder; + private final BuilderSupplier builder; - ElementType(IntFunction builder) { + ElementType(BuilderSupplier builder) { this.builder = builder; } /** * Create a new {@link Block.Builder} for blocks of this type. + * @deprecated use {@link #newBlockBuilder(int, BlockFactory)} */ + @Deprecated public Block.Builder newBlockBuilder(int estimatedSize) { - return builder.apply(estimatedSize); + return builder.newBlockBuilder(estimatedSize, BlockFactory.getNonBreakingInstance()); + } + + /** + * Create a new {@link Block.Builder} for blocks of this type. + */ + public Block.Builder newBlockBuilder(int estimatedSize, BlockFactory blockFactory) { + return builder.newBlockBuilder(estimatedSize, blockFactory); } public static ElementType fromJava(Class type) { diff --git a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/Vector.java b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/Vector.java index 171bdbd62f4d0..c9ecf1aa9e399 100644 --- a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/Vector.java +++ b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/Vector.java @@ -50,7 +50,11 @@ public interface Vector extends Accountable, Releasable { /** The block factory associated with this vector. */ BlockFactory blockFactory(); - interface Builder { + /** + * Builds {@link Vector}s. Typically, you use one of it's direct supinterfaces like {@link IntVector.Builder}. + * This is {@link Releasable} and should be released after building the vector or if building the vector fails. + */ + interface Builder extends Releasable { /** * Builds the block. This method can be called multiple times. */ diff --git a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/X-ArrayBlock.java.st b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/X-ArrayBlock.java.st index 10ff868c09806..ddb0eced039be 100644 --- a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/X-ArrayBlock.java.st +++ b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/X-ArrayBlock.java.st @@ -93,6 +93,7 @@ $endif$ public static long ramBytesEstimated($if(BytesRef)$BytesRefArray$else$$type$[]$endif$ values, int[] firstValueIndexes, BitSet nullsMask) { return BASE_RAM_BYTES_USED + RamUsageEstimator.sizeOf(values) + BlockRamUsageEstimator.sizeOf(firstValueIndexes) + BlockRamUsageEstimator.sizeOfBitSet(nullsMask) + RamUsageEstimator.shallowSizeOfInstance(MvOrdering.class); + // TODO mvordering is shared } @Override @@ -137,7 +138,7 @@ $endif$ } released = true; $if(BytesRef)$ - blockFactory.adjustBreaker(-(ramBytesUsed() - values.ramBytesUsed()), true); + blockFactory.adjustBreaker(-ramBytesUsed() + values.bigArraysRamBytesUsed(), true); Releasables.closeExpectNoException(values); $else$ blockFactory.adjustBreaker(-ramBytesUsed(), true); diff --git a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/X-ArrayVector.java.st b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/X-ArrayVector.java.st index b6a8714f882ee..3e6ccc2286675 100644 --- a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/X-ArrayVector.java.st +++ b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/X-ArrayVector.java.st @@ -110,7 +110,7 @@ $endif$ $if(BytesRef)$ @Override public void close() { - blockFactory.adjustBreaker(-BASE_RAM_BYTES_USED, true); + blockFactory.adjustBreaker(-ramBytesUsed() + values.bigArraysRamBytesUsed(), true); Releasables.closeExpectNoException(values); } $endif$ diff --git a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/X-BlockBuilder.java.st b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/X-BlockBuilder.java.st index 4d43f25577cc5..0ccfc45f18664 100644 --- a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/X-BlockBuilder.java.st +++ b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/X-BlockBuilder.java.st @@ -14,6 +14,8 @@ import org.elasticsearch.common.util.BytesRefArray; import org.elasticsearch.core.Releasables; $else$ +import org.apache.lucene.util.RamUsageEstimator; + import java.util.Arrays; $endif$ @@ -41,7 +43,7 @@ $else$ $Type$BlockBuilder(int estimatedSize, BlockFactory blockFactory) { super(blockFactory); int initialSize = Math.max(estimatedSize, 2); - adjustBreaker(initialSize); + adjustBreaker(RamUsageEstimator.NUM_BYTES_ARRAY_HEADER + initialSize * elementSize()); values = new $type$[initialSize]; } $endif$ @@ -246,27 +248,68 @@ $endif$ public $Type$Block build() { finish(); $Type$Block block; - if (hasNonNullValue && positionCount == 1 && valueCount == 1) { $if(BytesRef)$ + assert estimatedBytes == 0 || firstValueIndexes != null; + if (hasNonNullValue && positionCount == 1 && valueCount == 1) { block = new ConstantBytesRefVector(BytesRef.deepCopyOf(values.get(0, new BytesRef())), 1, blockFactory).asBlock(); + /* + * Update the breaker with the actual bytes used. + * We pass false below even though we've used the bytes. That's weird, + * but if we break here we will throw away the used memory, letting + * it be deallocated. The exception will bubble up and the builder will + * still technically be open, meaning the calling code should close it + * which will return all used memory to the breaker. + */ + blockFactory.adjustBreaker(block.ramBytesUsed() - estimatedBytes, false); Releasables.closeExpectNoException(values); } else { - estimatedBytes += values.ramBytesUsed(); + if (isDense() && singleValued()) { + block = new $Type$ArrayVector(values, positionCount, blockFactory).asBlock(); + } else { + block = new $Type$ArrayBlock(values, positionCount, firstValueIndexes, nullsMask, mvOrdering, blockFactory); + } + /* + * Update the breaker with the actual bytes used. + * We pass false below even though we've used the bytes. That's weird, + * but if we break here we will throw away the used memory, letting + * it be deallocated. The exception will bubble up and the builder will + * still technically be open, meaning the calling code should close it + * which will return all used memory to the breaker. + */ + blockFactory.adjustBreaker(block.ramBytesUsed() - estimatedBytes - values.bigArraysRamBytesUsed(), false); + } + values = null; $else$ + if (hasNonNullValue && positionCount == 1 && valueCount == 1) { block = new Constant$Type$Vector(values[0], 1, blockFactory).asBlock(); } else { if (values.length - valueCount > 1024 || valueCount < (values.length / 2)) { values = Arrays.copyOf(values, valueCount); } -$endif$ if (isDense() && singleValued()) { block = new $Type$ArrayVector(values, positionCount, blockFactory).asBlock(); } else { block = new $Type$ArrayBlock(values, positionCount, firstValueIndexes, nullsMask, mvOrdering, blockFactory); } } - // update the breaker with the actual bytes used. - blockFactory.adjustBreaker(block.ramBytesUsed() - estimatedBytes, true); + /* + * Update the breaker with the actual bytes used. + * We pass false below even though we've used the bytes. That's weird, + * but if we break here we will throw away the used memory, letting + * it be deallocated. The exception will bubble up and the builder will + * still technically be open, meaning the calling code should close it + * which will return all used memory to the breaker. + */ + blockFactory.adjustBreaker(block.ramBytesUsed() - estimatedBytes, false); +$endif$ + built(); return block; } +$if(BytesRef)$ + + @Override + public void extraClose() { + Releasables.closeExpectNoException(values); + } +$endif$ } diff --git a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/X-VectorBuilder.java.st b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/X-VectorBuilder.java.st index b813120b42e43..1e243c49b5d82 100644 --- a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/X-VectorBuilder.java.st +++ b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/X-VectorBuilder.java.st @@ -83,24 +83,62 @@ $endif$ @Override public $Type$Vector build() { + finish(); $Type$Vector vector; - if (valueCount == 1) { $if(BytesRef)$ + assert estimatedBytes == 0; + if (valueCount == 1) { vector = new ConstantBytesRefVector(BytesRef.deepCopyOf(values.get(0, new BytesRef())), 1, blockFactory); + /* + * Update the breaker with the actual bytes used. + * We pass false below even though we've used the bytes. That's weird, + * but if we break here we will throw away the used memory, letting + * it be deallocated. The exception will bubble up and the builder will + * still technically be open, meaning the calling code should close it + * which will return all used memory to the breaker. + */ + blockFactory.adjustBreaker(vector.ramBytesUsed(), false); Releasables.closeExpectNoException(values); } else { - estimatedBytes = values.ramBytesUsed(); + vector = new $Type$ArrayVector(values, valueCount, blockFactory); + /* + * Update the breaker with the actual bytes used. + * We pass false below even though we've used the bytes. That's weird, + * but if we break here we will throw away the used memory, letting + * it be deallocated. The exception will bubble up and the builder will + * still technically be open, meaning the calling code should close it + * which will return all used memory to the breaker. + */ + blockFactory.adjustBreaker(vector.ramBytesUsed() - values.bigArraysRamBytesUsed(), false); + } + values = null; $else$ + if (valueCount == 1) { vector = new Constant$Type$Vector(values[0], 1, blockFactory); } else { if (values.length - valueCount > 1024 || valueCount < (values.length / 2)) { values = Arrays.copyOf(values, valueCount); } -$endif$ vector = new $Type$ArrayVector(values, valueCount, blockFactory); } - // update the breaker with the actual bytes used. - blockFactory.adjustBreaker(vector.ramBytesUsed() - estimatedBytes, true); + /* + * Update the breaker with the actual bytes used. + * We pass false below even though we've used the bytes. That's weird, + * but if we break here we will throw away the used memory, letting + * it be deallocated. The exception will bubble up and the builder will + * still technically be open, meaning the calling code should close it + * which will return all used memory to the breaker. + */ + blockFactory.adjustBreaker(vector.ramBytesUsed() - estimatedBytes, false); +$endif$ + built(); return vector; } +$if(BytesRef)$ + + @Override + public void extraClose() { + Releasables.closeExpectNoException(values); + } +$endif$ } diff --git a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/X-VectorFixedBuilder.java.st b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/X-VectorFixedBuilder.java.st index 86bc6b0a095d6..d732c85db7467 100644 --- a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/X-VectorFixedBuilder.java.st +++ b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/X-VectorFixedBuilder.java.st @@ -58,4 +58,12 @@ final class $Type$VectorFixedBuilder implements $Type$Vector.FixedBuilder { } return new $Type$ArrayVector(values, values.length, blockFactory); } + + @Override + public void close() { + if (nextIndex >= 0) { + // If nextIndex < 0 we've already built the vector + blockFactory.adjustBreaker(-ramBytesUsed(values.length), false); + } + } } diff --git a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/operator/Driver.java b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/operator/Driver.java index 281693a487255..1a1604406892c 100644 --- a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/operator/Driver.java +++ b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/operator/Driver.java @@ -181,7 +181,13 @@ private SubscribableListener runSingleLoopIteration() { if (op.isFinished() == false && nextOp.needsInput()) { Page page = op.getOutput(); - if (page != null && page.getPositionCount() != 0) { + if (page == null) { + // No result, just move to the next iteration + } else if (page.getPositionCount() == 0) { + // Empty result, release any memory it holds immediately and move to the next iteration + page.releaseBlocks(); + } else { + // Non-empty result from the previous operation, move it to the next operation nextOp.addInput(page); movedPage = true; } diff --git a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/operator/exchange/ExchangeSinkOperator.java b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/operator/exchange/ExchangeSinkOperator.java index d52f25e9d8306..0fb6ec6f63d96 100644 --- a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/operator/exchange/ExchangeSinkOperator.java +++ b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/operator/exchange/ExchangeSinkOperator.java @@ -36,10 +36,6 @@ public record ExchangeSinkOperatorFactory(Supplier exchangeSinks, implements SinkOperatorFactory { - public ExchangeSinkOperatorFactory(Supplier exchangeSinks) { - this(exchangeSinks, Function.identity()); - } - @Override public SinkOperator get(DriverContext driverContext) { return new ExchangeSinkOperator(exchangeSinks.get(), transformer); diff --git a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/operator/topn/ResultBuilder.java b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/operator/topn/ResultBuilder.java index b8a41a3ee343d..bd2027cade78f 100644 --- a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/operator/topn/ResultBuilder.java +++ b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/operator/topn/ResultBuilder.java @@ -9,12 +9,14 @@ import org.apache.lucene.util.BytesRef; import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.BlockFactory; import org.elasticsearch.compute.data.ElementType; +import org.elasticsearch.core.Releasable; /** * Builds {@link Block}s from keys and values encoded into {@link BytesRef}s. */ -interface ResultBuilder { +interface ResultBuilder extends Releasable { /** * Called for each sort key before {@link #decodeValue} to consume the sort key and * store the value of the key for {@link #decodeValue} can use it to reconstruct @@ -36,15 +38,21 @@ interface ResultBuilder { */ Block build(); - static ResultBuilder resultBuilderFor(ElementType elementType, TopNEncoder encoder, boolean inKey, int positions) { + static ResultBuilder resultBuilderFor( + BlockFactory blockFactory, + ElementType elementType, + TopNEncoder encoder, + boolean inKey, + int positions + ) { return switch (elementType) { - case BOOLEAN -> new ResultBuilderForBoolean(encoder, inKey, positions); - case BYTES_REF -> new ResultBuilderForBytesRef(encoder, inKey, positions); - case INT -> new ResultBuilderForInt(encoder, inKey, positions); - case LONG -> new ResultBuilderForLong(encoder, inKey, positions); - case DOUBLE -> new ResultBuilderForDouble(encoder, inKey, positions); - case NULL -> new ResultBuilderForNull(); - case DOC -> new ResultBuilderForDoc(positions); + case BOOLEAN -> new ResultBuilderForBoolean(blockFactory, encoder, inKey, positions); + case BYTES_REF -> new ResultBuilderForBytesRef(blockFactory, encoder, inKey, positions); + case INT -> new ResultBuilderForInt(blockFactory, encoder, inKey, positions); + case LONG -> new ResultBuilderForLong(blockFactory, encoder, inKey, positions); + case DOUBLE -> new ResultBuilderForDouble(blockFactory, encoder, inKey, positions); + case NULL -> new ResultBuilderForNull(blockFactory); + case DOC -> new ResultBuilderForDoc(blockFactory, positions); default -> { assert false : "Result builder for [" + elementType + "]"; throw new UnsupportedOperationException("Result builder for [" + elementType + "]"); diff --git a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/operator/topn/ResultBuilderForDoc.java b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/operator/topn/ResultBuilderForDoc.java index 166d5be83b474..7fb507ffdbead 100644 --- a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/operator/topn/ResultBuilderForDoc.java +++ b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/operator/topn/ResultBuilderForDoc.java @@ -13,12 +13,15 @@ import org.elasticsearch.compute.data.DocVector; class ResultBuilderForDoc implements ResultBuilder { + private final BlockFactory blockFactory; private final int[] shards; private final int[] segments; private final int[] docs; private int position; - ResultBuilderForDoc(int positions) { + ResultBuilderForDoc(BlockFactory blockFactory, int positions) { + // TODO use fixed length builders + this.blockFactory = blockFactory; this.shards = new int[positions]; this.segments = new int[positions]; this.docs = new int[positions]; @@ -40,9 +43,9 @@ public void decodeValue(BytesRef values) { @Override public Block build() { return new DocVector( - BlockFactory.getNonBreakingInstance().newIntArrayVector(shards, position), - BlockFactory.getNonBreakingInstance().newIntArrayVector(segments, position), - BlockFactory.getNonBreakingInstance().newIntArrayVector(docs, position), + blockFactory.newIntArrayVector(shards, position), + blockFactory.newIntArrayVector(segments, position), + blockFactory.newIntArrayVector(docs, position), null ).asBlock(); } @@ -51,4 +54,9 @@ public Block build() { public String toString() { return "ValueExtractorForDoc"; } + + @Override + public void close() { + // TODO memory accounting + } } diff --git a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/operator/topn/ResultBuilderForNull.java b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/operator/topn/ResultBuilderForNull.java index 05b9ba2a07658..a45f16fc30910 100644 --- a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/operator/topn/ResultBuilderForNull.java +++ b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/operator/topn/ResultBuilderForNull.java @@ -9,10 +9,16 @@ import org.apache.lucene.util.BytesRef; import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.BlockFactory; public class ResultBuilderForNull implements ResultBuilder { + private final BlockFactory blockFactory; private int positions; + public ResultBuilderForNull(BlockFactory blockFactory) { + this.blockFactory = blockFactory; + } + @Override public void decodeKey(BytesRef keys) { throw new AssertionError("somehow got a value for a null key"); @@ -29,11 +35,16 @@ public void decodeValue(BytesRef values) { @Override public Block build() { - return Block.constantNullBlock(positions); + return Block.constantNullBlock(positions, blockFactory); } @Override public String toString() { return "ValueExtractorForNull"; } + + @Override + public void close() { + // Nothing to close + } } diff --git a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/operator/topn/TopNOperator.java b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/operator/topn/TopNOperator.java index 86b3a18992db4..9657d60376763 100644 --- a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/operator/topn/TopNOperator.java +++ b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/operator/topn/TopNOperator.java @@ -12,7 +12,9 @@ import org.apache.lucene.util.PriorityQueue; import org.apache.lucene.util.RamUsageEstimator; import org.elasticsearch.common.breaker.CircuitBreaker; +import org.elasticsearch.common.collect.Iterators; import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.BlockFactory; import org.elasticsearch.compute.data.ElementType; import org.elasticsearch.compute.data.Page; import org.elasticsearch.compute.operator.BreakingBytesRefBuilder; @@ -205,7 +207,15 @@ public record TopNOperatorFactory( @Override public TopNOperator get(DriverContext driverContext) { - return new TopNOperator(driverContext.breaker(), topCount, elementTypes, encoders, sortOrders, maxPageSize); + return new TopNOperator( + driverContext.blockFactory(), + driverContext.breaker(), + topCount, + elementTypes, + encoders, + sortOrders, + maxPageSize + ); } @Override @@ -222,6 +232,7 @@ public String describe() { } } + private final BlockFactory blockFactory; private final CircuitBreaker breaker; private final Queue inputQueue; @@ -231,9 +242,11 @@ public String describe() { private final List encoders; private final List sortOrders; + private Row spare; private Iterator output; public TopNOperator( + BlockFactory blockFactory, CircuitBreaker breaker, int topCount, List elementTypes, @@ -241,6 +254,7 @@ public TopNOperator( List sortOrders, int maxPageSize ) { + this.blockFactory = blockFactory; this.breaker = breaker; this.maxPageSize = maxPageSize; this.elementTypes = elementTypes; @@ -301,21 +315,20 @@ public void addInput(Page page) { * and must be closed. That happens either because it's overflow from the * inputQueue or because we hit an allocation failure while building it. */ - Row row = null; try { for (int i = 0; i < page.getPositionCount(); i++) { - if (row == null) { - row = new Row(breaker); + if (spare == null) { + spare = new Row(breaker); } else { - row.keys.clear(); - row.orderByCompositeKeyAscending.clear(); - row.values.clear(); + spare.keys.clear(); + spare.orderByCompositeKeyAscending.clear(); + spare.values.clear(); } - rowFiller.row(i, row); - row = inputQueue.insertWithOverflow(row); + rowFiller.row(i, spare); + spare = inputQueue.insertWithOverflow(spare); } } finally { - Releasables.close(row); + Releasables.close(() -> page.releaseBlocks()); } } @@ -327,18 +340,24 @@ public void finish() { } private Iterator toPages() { + if (spare != null) { + // Remove the spare, we're never going to use it again. + spare.close(); + spare = null; + } if (inputQueue.size() == 0) { return Collections.emptyIterator(); } List list = new ArrayList<>(inputQueue.size()); + List result = new ArrayList<>(); + ResultBuilder[] builders = null; + boolean success = false; try { while (inputQueue.size() > 0) { list.add(inputQueue.pop()); } Collections.reverse(list); - List result = new ArrayList<>(); - ResultBuilder[] builders = null; int p = 0; int size = 0; for (int i = 0; i < list.size(); i++) { @@ -347,6 +366,7 @@ private Iterator toPages() { builders = new ResultBuilder[elementTypes.size()]; for (int b = 0; b < builders.length; b++) { builders[b] = ResultBuilder.resultBuilderFor( + blockFactory, elementTypes.get(b), encoders.get(b).toUnsortable(), channelInKey(sortOrders, b), @@ -386,14 +406,22 @@ private Iterator toPages() { p++; if (p == size) { result.add(new Page(Arrays.stream(builders).map(ResultBuilder::build).toArray(Block[]::new))); + Releasables.closeExpectNoException(builders); builders = null; } - } assert builders == null; + success = true; return result.iterator(); } finally { - Releasables.closeExpectNoException(() -> Releasables.close(list)); + if (success == false) { + List close = new ArrayList<>(list); + for (Page p : result) { + close.add(p::releaseBlocks); + } + Collections.addAll(close, builders); + Releasables.closeExpectNoException(Releasables.wrap(close)); + } } } @@ -422,10 +450,15 @@ public Page getOutput() { @Override public void close() { /* - * If everything went well we'll have drained inputQueue to this'll - * be a noop. But if inputQueue + * If we close before calling finish then spare and inputQueue will be live rows + * that need closing. If we close after calling finish then the output iterator + * will contain pages of results that have yet to be returned. */ - Releasables.closeExpectNoException(() -> Releasables.close(inputQueue)); + Releasables.closeExpectNoException( + spare, + inputQueue == null ? null : Releasables.wrap(inputQueue), + output == null ? null : Releasables.wrap(() -> Iterators.map(output, p -> p::releaseBlocks)) + ); } private static long SHALLOW_SIZE = RamUsageEstimator.shallowSizeOfInstance(TopNOperator.class) + RamUsageEstimator diff --git a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/operator/topn/X-ResultBuilder.java.st b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/operator/topn/X-ResultBuilder.java.st index 5f9a35bd0ebd3..ebe62398c8504 100644 --- a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/operator/topn/X-ResultBuilder.java.st +++ b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/operator/topn/X-ResultBuilder.java.st @@ -8,6 +8,7 @@ package org.elasticsearch.compute.operator.topn; import org.apache.lucene.util.BytesRef; +import org.elasticsearch.compute.data.BlockFactory; import org.elasticsearch.compute.data.$Type$Block; class ResultBuilderFor$Type$ implements ResultBuilder { @@ -26,14 +27,14 @@ $endif$ */ private $type$ key; - ResultBuilderFor$Type$(TopNEncoder encoder, boolean inKey, int initialSize) { + ResultBuilderFor$Type$(BlockFactory blockFactory, TopNEncoder encoder, boolean inKey, int initialSize) { $if(BytesRef)$ this.encoder = encoder; $else$ assert encoder == TopNEncoder.DEFAULT_UNSORTABLE : encoder.toString(); $endif$ this.inKey = inKey; - this.builder = $Type$Block.newBlockBuilder(initialSize); + this.builder = $Type$Block.newBlockBuilder(initialSize, blockFactory); } @Override @@ -81,4 +82,9 @@ $endif$ public String toString() { return "ResultBuilderFor$Type$[inKey=" + inKey + "]"; } + + @Override + public void close() { + builder.close(); + } } diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/OperatorTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/OperatorTests.java index 04a966b399870..bdf696f460060 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/OperatorTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/OperatorTests.java @@ -287,7 +287,7 @@ public void testLimitOperator() { try ( var driver = new Driver( driverContext, - new SequenceLongBlockSourceOperator(values, 100), + new SequenceLongBlockSourceOperator(driverContext.blockFactory(), values, 100), List.of((new LimitOperator.Factory(limit)).get(driverContext)), new PageConsumerOperator(page -> { LongBlock block = page.getBlock(0); diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/AggregatorFunctionTestCase.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/AggregatorFunctionTestCase.java index a4b6c8b965962..22325039af124 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/AggregatorFunctionTestCase.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/AggregatorFunctionTestCase.java @@ -91,8 +91,8 @@ protected final ByteSizeValue smallEnoughToCircuitBreak() { public final void testIgnoresNulls() { int end = between(1_000, 100_000); List results = new ArrayList<>(); - List input = CannedSourceOperator.collectPages(simpleInput(end)); DriverContext driverContext = driverContext(); + List input = CannedSourceOperator.collectPages(simpleInput(driverContext.blockFactory(), end)); try ( Driver d = new Driver( @@ -111,7 +111,9 @@ public final void testIgnoresNulls() { public final void testMultivalued() { int end = between(1_000, 100_000); DriverContext driverContext = driverContext(); - List input = CannedSourceOperator.collectPages(new PositionMergingSourceOperator(simpleInput(end))); + List input = CannedSourceOperator.collectPages( + new PositionMergingSourceOperator(simpleInput(driverContext.blockFactory(), end)) + ); assertSimpleOutput(input, drive(simple(BigArrays.NON_RECYCLING_INSTANCE).get(driverContext), input.iterator())); } @@ -119,7 +121,7 @@ public final void testMultivaluedWithNulls() { int end = between(1_000, 100_000); DriverContext driverContext = driverContext(); List input = CannedSourceOperator.collectPages( - new NullInsertingSourceOperator(new PositionMergingSourceOperator(simpleInput(end))) + new NullInsertingSourceOperator(new PositionMergingSourceOperator(simpleInput(driverContext.blockFactory(), end))) ); assertSimpleOutput(input, drive(simple(BigArrays.NON_RECYCLING_INSTANCE).get(driverContext), input.iterator())); } diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/CountAggregatorFunctionTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/CountAggregatorFunctionTests.java index 11241020a6709..623de7fdd1fff 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/CountAggregatorFunctionTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/CountAggregatorFunctionTests.java @@ -10,6 +10,7 @@ import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.compute.data.BasicBlockTests; import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.BlockFactory; import org.elasticsearch.compute.data.LongBlock; import org.elasticsearch.compute.operator.SequenceLongBlockSourceOperator; import org.elasticsearch.compute.operator.SourceOperator; @@ -21,8 +22,8 @@ public class CountAggregatorFunctionTests extends AggregatorFunctionTestCase { @Override - protected SourceOperator simpleInput(int size) { - return new SequenceLongBlockSourceOperator(LongStream.range(0, size).map(l -> randomLong())); + protected SourceOperator simpleInput(BlockFactory blockFactory, int size) { + return new SequenceLongBlockSourceOperator(blockFactory, LongStream.range(0, size).map(l -> randomLong())); } @Override diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/CountDistinctBooleanAggregatorFunctionTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/CountDistinctBooleanAggregatorFunctionTests.java index 74cd88feed3f4..febbb4d4a0615 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/CountDistinctBooleanAggregatorFunctionTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/CountDistinctBooleanAggregatorFunctionTests.java @@ -10,6 +10,7 @@ import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.compute.data.BasicBlockTests; import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.BlockFactory; import org.elasticsearch.compute.data.LongBlock; import org.elasticsearch.compute.operator.SequenceBooleanBlockSourceOperator; import org.elasticsearch.compute.operator.SourceOperator; @@ -21,7 +22,7 @@ public class CountDistinctBooleanAggregatorFunctionTests extends AggregatorFunctionTestCase { @Override - protected SourceOperator simpleInput(int size) { + protected SourceOperator simpleInput(BlockFactory blockFactory, int size) { return new SequenceBooleanBlockSourceOperator(LongStream.range(0, size).mapToObj(l -> randomBoolean()).toList()); } diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/CountDistinctBooleanGroupingAggregatorFunctionTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/CountDistinctBooleanGroupingAggregatorFunctionTests.java index eab1b9cb2d8de..7360b101bf79d 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/CountDistinctBooleanGroupingAggregatorFunctionTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/CountDistinctBooleanGroupingAggregatorFunctionTests.java @@ -9,6 +9,7 @@ import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.BlockFactory; import org.elasticsearch.compute.data.LongBlock; import org.elasticsearch.compute.data.Page; import org.elasticsearch.compute.operator.LongBooleanTupleBlockSourceOperator; @@ -33,7 +34,7 @@ protected String expectedDescriptionOfAggregator() { } @Override - protected SourceOperator simpleInput(int size) { + protected SourceOperator simpleInput(BlockFactory blockFactory, int size) { return new LongBooleanTupleBlockSourceOperator( LongStream.range(0, size).mapToObj(l -> Tuple.tuple(randomGroupId(size), randomBoolean())) ); diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/CountDistinctBytesRefAggregatorFunctionTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/CountDistinctBytesRefAggregatorFunctionTests.java index 69ccc0a04c0f9..c495a6b9f196b 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/CountDistinctBytesRefAggregatorFunctionTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/CountDistinctBytesRefAggregatorFunctionTests.java @@ -11,6 +11,7 @@ import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.compute.data.BasicBlockTests; import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.BlockFactory; import org.elasticsearch.compute.data.LongBlock; import org.elasticsearch.compute.operator.BytesRefBlockSourceOperator; import org.elasticsearch.compute.operator.SourceOperator; @@ -23,7 +24,7 @@ public class CountDistinctBytesRefAggregatorFunctionTests extends AggregatorFunctionTestCase { @Override - protected SourceOperator simpleInput(int size) { + protected SourceOperator simpleInput(BlockFactory blockFactory, int size) { int max = between(1, Math.min(Integer.MAX_VALUE, Integer.MAX_VALUE / size)); return new BytesRefBlockSourceOperator( LongStream.range(0, size).mapToObj(l -> new BytesRef(String.valueOf(between(-max, max)))).toList() diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/CountDistinctBytesRefGroupingAggregatorFunctionTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/CountDistinctBytesRefGroupingAggregatorFunctionTests.java index 919d06af430fd..eadbba9f91880 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/CountDistinctBytesRefGroupingAggregatorFunctionTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/CountDistinctBytesRefGroupingAggregatorFunctionTests.java @@ -10,6 +10,7 @@ import org.apache.lucene.util.BytesRef; import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.BlockFactory; import org.elasticsearch.compute.data.LongBlock; import org.elasticsearch.compute.data.Page; import org.elasticsearch.compute.operator.LongBytesRefTupleBlockSourceOperator; @@ -35,7 +36,7 @@ protected String expectedDescriptionOfAggregator() { } @Override - protected SourceOperator simpleInput(int size) { + protected SourceOperator simpleInput(BlockFactory blockFactory, int size) { return new LongBytesRefTupleBlockSourceOperator( LongStream.range(0, size).mapToObj(l -> Tuple.tuple(randomGroupId(size), new BytesRef(String.valueOf(between(1, 10000))))) ); diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/CountDistinctDoubleAggregatorFunctionTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/CountDistinctDoubleAggregatorFunctionTests.java index c0678441cdc74..ccfe7b426ebca 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/CountDistinctDoubleAggregatorFunctionTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/CountDistinctDoubleAggregatorFunctionTests.java @@ -10,6 +10,7 @@ import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.compute.data.BasicBlockTests; import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.BlockFactory; import org.elasticsearch.compute.data.LongBlock; import org.elasticsearch.compute.operator.SequenceDoubleBlockSourceOperator; import org.elasticsearch.compute.operator.SourceOperator; @@ -23,7 +24,7 @@ public class CountDistinctDoubleAggregatorFunctionTests extends AggregatorFunctionTestCase { @Override - protected SourceOperator simpleInput(int size) { + protected SourceOperator simpleInput(BlockFactory blockFactory, int size) { return new SequenceDoubleBlockSourceOperator(LongStream.range(0, size).mapToDouble(l -> ESTestCase.randomDouble())); } diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/CountDistinctDoubleGroupingAggregatorFunctionTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/CountDistinctDoubleGroupingAggregatorFunctionTests.java index 5a928f12d33b7..0c4d89da09b99 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/CountDistinctDoubleGroupingAggregatorFunctionTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/CountDistinctDoubleGroupingAggregatorFunctionTests.java @@ -9,6 +9,7 @@ import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.BlockFactory; import org.elasticsearch.compute.data.LongBlock; import org.elasticsearch.compute.data.Page; import org.elasticsearch.compute.operator.LongDoubleTupleBlockSourceOperator; @@ -34,7 +35,7 @@ protected String expectedDescriptionOfAggregator() { } @Override - protected SourceOperator simpleInput(int size) { + protected SourceOperator simpleInput(BlockFactory blockFactory, int size) { return new LongDoubleTupleBlockSourceOperator( LongStream.range(0, size).mapToObj(l -> Tuple.tuple(randomGroupId(size), randomDoubleBetween(0, 100, true))) ); diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/CountDistinctIntAggregatorFunctionTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/CountDistinctIntAggregatorFunctionTests.java index 3699a87431937..b67e4cdee7e97 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/CountDistinctIntAggregatorFunctionTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/CountDistinctIntAggregatorFunctionTests.java @@ -29,7 +29,7 @@ public class CountDistinctIntAggregatorFunctionTests extends AggregatorFunctionTestCase { @Override - protected SourceOperator simpleInput(int size) { + protected SourceOperator simpleInput(BlockFactory blockFactory, int size) { int max = between(1, Math.min(Integer.MAX_VALUE, Integer.MAX_VALUE / size)); return new SequenceIntBlockSourceOperator(LongStream.range(0, size).mapToInt(l -> between(-max, max))); } diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/CountDistinctIntGroupingAggregatorFunctionTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/CountDistinctIntGroupingAggregatorFunctionTests.java index f2a46e9f4c3af..678024c19d391 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/CountDistinctIntGroupingAggregatorFunctionTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/CountDistinctIntGroupingAggregatorFunctionTests.java @@ -9,6 +9,7 @@ import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.BlockFactory; import org.elasticsearch.compute.data.LongBlock; import org.elasticsearch.compute.data.Page; import org.elasticsearch.compute.operator.LongIntBlockSourceOperator; @@ -34,7 +35,7 @@ protected String expectedDescriptionOfAggregator() { } @Override - protected SourceOperator simpleInput(int size) { + protected SourceOperator simpleInput(BlockFactory blockFactory, int size) { return new LongIntBlockSourceOperator(LongStream.range(0, size).mapToObj(l -> Tuple.tuple(randomGroupId(size), between(0, 10000)))); } diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/CountDistinctLongAggregatorFunctionTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/CountDistinctLongAggregatorFunctionTests.java index 556f9d0ccc462..704b5c649f744 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/CountDistinctLongAggregatorFunctionTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/CountDistinctLongAggregatorFunctionTests.java @@ -30,9 +30,9 @@ public class CountDistinctLongAggregatorFunctionTests extends AggregatorFunctionTestCase { @Override - protected SourceOperator simpleInput(int size) { + protected SourceOperator simpleInput(BlockFactory blockFactory, int size) { long max = randomLongBetween(1, Long.MAX_VALUE / size); - return new SequenceLongBlockSourceOperator(LongStream.range(0, size).map(l -> randomLongBetween(-max, max))); + return new SequenceLongBlockSourceOperator(blockFactory, LongStream.range(0, size).map(l -> randomLongBetween(-max, max))); } @Override diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/CountDistinctLongGroupingAggregatorFunctionTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/CountDistinctLongGroupingAggregatorFunctionTests.java index a5959471b8e15..4282adaba595e 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/CountDistinctLongGroupingAggregatorFunctionTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/CountDistinctLongGroupingAggregatorFunctionTests.java @@ -9,6 +9,7 @@ import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.BlockFactory; import org.elasticsearch.compute.data.LongBlock; import org.elasticsearch.compute.data.Page; import org.elasticsearch.compute.operator.SourceOperator; @@ -33,8 +34,9 @@ protected String expectedDescriptionOfAggregator() { } @Override - protected SourceOperator simpleInput(int size) { + protected SourceOperator simpleInput(BlockFactory blockFactory, int size) { return new TupleBlockSourceOperator( + blockFactory, LongStream.range(0, size).mapToObj(l -> Tuple.tuple(randomGroupId(size), randomLongBetween(0, 100_000))) ); } diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/CountGroupingAggregatorFunctionTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/CountGroupingAggregatorFunctionTests.java index 54a35fcc19cb2..945c68711bb4e 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/CountGroupingAggregatorFunctionTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/CountGroupingAggregatorFunctionTests.java @@ -9,6 +9,7 @@ import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.BlockFactory; import org.elasticsearch.compute.data.LongBlock; import org.elasticsearch.compute.data.Page; import org.elasticsearch.compute.operator.LongDoubleTupleBlockSourceOperator; @@ -33,9 +34,10 @@ protected String expectedDescriptionOfAggregator() { } @Override - protected SourceOperator simpleInput(int size) { + protected SourceOperator simpleInput(BlockFactory blockFactory, int size) { if (randomBoolean()) { return new TupleBlockSourceOperator( + blockFactory, LongStream.range(0, size).mapToObj(l -> Tuple.tuple(randomLongBetween(0, 4), randomLong())) ); } diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/GroupingAggregatorFunctionTestCase.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/GroupingAggregatorFunctionTestCase.java index eab6eb30261bd..4ae58fd8c6333 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/GroupingAggregatorFunctionTestCase.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/GroupingAggregatorFunctionTestCase.java @@ -147,7 +147,9 @@ protected ByteSizeValue smallEnoughToCircuitBreak() { public final void testNullGroupsAndValues() { DriverContext driverContext = driverContext(); int end = between(50, 60); - List input = CannedSourceOperator.collectPages(new NullInsertingSourceOperator(simpleInput(end))); + List input = CannedSourceOperator.collectPages( + new NullInsertingSourceOperator(simpleInput(driverContext.blockFactory(), end)) + ); List results = drive(simple(nonBreakingBigArrays().withCircuitBreaking()).get(driverContext), input.iterator()); assertSimpleOutput(input, results); } @@ -155,7 +157,7 @@ public final void testNullGroupsAndValues() { public final void testNullGroups() { DriverContext driverContext = driverContext(); int end = between(50, 60); - List input = CannedSourceOperator.collectPages(nullGroups(simpleInput(end))); + List input = CannedSourceOperator.collectPages(nullGroups(simpleInput(driverContext.blockFactory(), end))); List results = drive(simple(nonBreakingBigArrays().withCircuitBreaking()).get(driverContext), input.iterator()); assertSimpleOutput(input, results); } @@ -184,7 +186,7 @@ protected void appendNull(ElementType elementType, Block.Builder builder, int bl public final void testNullValues() { DriverContext driverContext = driverContext(); int end = between(50, 60); - List input = CannedSourceOperator.collectPages(nullValues(simpleInput(end))); + List input = CannedSourceOperator.collectPages(nullValues(simpleInput(driverContext.blockFactory(), end))); List results = drive(simple(nonBreakingBigArrays().withCircuitBreaking()).get(driverContext), input.iterator()); assertSimpleOutput(input, results); } @@ -192,7 +194,7 @@ public final void testNullValues() { public final void testNullValuesInitialIntermediateFinal() { DriverContext driverContext = driverContext(); int end = between(50, 60); - List input = CannedSourceOperator.collectPages(nullValues(simpleInput(end))); + List input = CannedSourceOperator.collectPages(nullValues(simpleInput(driverContext.blockFactory(), end))); List results = drive( List.of( simpleWithMode(nonBreakingBigArrays().withCircuitBreaking(), AggregatorMode.INITIAL).get(driverContext), @@ -220,7 +222,7 @@ protected void appendNull(ElementType elementType, Block.Builder builder, int bl public final void testMultivalued() { DriverContext driverContext = driverContext(); int end = between(1_000, 100_000); - List input = CannedSourceOperator.collectPages(mergeValues(simpleInput(end))); + List input = CannedSourceOperator.collectPages(mergeValues(simpleInput(driverContext.blockFactory(), end))); List results = drive(simple(nonBreakingBigArrays().withCircuitBreaking()).get(driverContext), input.iterator()); assertSimpleOutput(input, results); } @@ -228,7 +230,9 @@ public final void testMultivalued() { public final void testMulitvaluedNullGroupsAndValues() { DriverContext driverContext = driverContext(); int end = between(50, 60); - List input = CannedSourceOperator.collectPages(new NullInsertingSourceOperator(mergeValues(simpleInput(end)))); + List input = CannedSourceOperator.collectPages( + new NullInsertingSourceOperator(mergeValues(simpleInput(driverContext.blockFactory(), end))) + ); List results = drive(simple(nonBreakingBigArrays().withCircuitBreaking()).get(driverContext), input.iterator()); assertSimpleOutput(input, results); } @@ -236,7 +240,7 @@ public final void testMulitvaluedNullGroupsAndValues() { public final void testMulitvaluedNullGroup() { DriverContext driverContext = driverContext(); int end = between(50, 60); - List input = CannedSourceOperator.collectPages(nullGroups(mergeValues(simpleInput(end)))); + List input = CannedSourceOperator.collectPages(nullGroups(mergeValues(simpleInput(driverContext.blockFactory(), end)))); List results = drive(simple(nonBreakingBigArrays().withCircuitBreaking()).get(driverContext), input.iterator()); assertSimpleOutput(input, results); } @@ -244,7 +248,7 @@ public final void testMulitvaluedNullGroup() { public final void testMulitvaluedNullValues() { DriverContext driverContext = driverContext(); int end = between(50, 60); - List input = CannedSourceOperator.collectPages(nullValues(mergeValues(simpleInput(end)))); + List input = CannedSourceOperator.collectPages(nullValues(mergeValues(simpleInput(driverContext.blockFactory(), end)))); List results = drive(simple(nonBreakingBigArrays().withCircuitBreaking()).get(driverContext), input.iterator()); assertSimpleOutput(input, results); } @@ -295,12 +299,13 @@ private void assertNullOnly(List operators) { public final void testNullSome() { DriverContext driverContext = driverContext(); - assertNullSome(List.of(simple(nonBreakingBigArrays().withCircuitBreaking()).get(driverContext))); + assertNullSome(driverContext, List.of(simple(nonBreakingBigArrays().withCircuitBreaking()).get(driverContext))); } public final void testNullSomeInitialFinal() { DriverContext driverContext = driverContext(); assertNullSome( + driverContext, List.of( simpleWithMode(nonBreakingBigArrays().withCircuitBreaking(), AggregatorMode.INITIAL).get(driverContext), simpleWithMode(nonBreakingBigArrays().withCircuitBreaking(), AggregatorMode.FINAL).get(driverContext) @@ -311,6 +316,7 @@ public final void testNullSomeInitialFinal() { public final void testNullSomeInitialIntermediateFinal() { DriverContext driverContext = driverContext(); assertNullSome( + driverContext, List.of( simpleWithMode(nonBreakingBigArrays().withCircuitBreaking(), AggregatorMode.INITIAL).get(driverContext), simpleWithMode(nonBreakingBigArrays().withCircuitBreaking(), AggregatorMode.INTERMEDIATE).get(driverContext), @@ -322,8 +328,8 @@ public final void testNullSomeInitialIntermediateFinal() { /** * Run the agg on some data where one group is always null. */ - private void assertNullSome(List operators) { - List inputData = CannedSourceOperator.collectPages(simpleInput(1000)); + private void assertNullSome(DriverContext driverContext, List operators) { + List inputData = CannedSourceOperator.collectPages(simpleInput(driverContext.blockFactory(), 1000)); SeenGroups seenGroups = seenGroups(inputData); long nullGroup = randomFrom(seenGroups.nonNull); diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/MaxDoubleAggregatorFunctionTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/MaxDoubleAggregatorFunctionTests.java index b67220b4909b7..cfda483d029f6 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/MaxDoubleAggregatorFunctionTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/MaxDoubleAggregatorFunctionTests.java @@ -9,6 +9,7 @@ import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.BlockFactory; import org.elasticsearch.compute.data.DoubleBlock; import org.elasticsearch.compute.operator.SequenceDoubleBlockSourceOperator; import org.elasticsearch.compute.operator.SourceOperator; @@ -21,7 +22,7 @@ public class MaxDoubleAggregatorFunctionTests extends AggregatorFunctionTestCase { @Override - protected SourceOperator simpleInput(int size) { + protected SourceOperator simpleInput(BlockFactory blockFactory, int size) { return new SequenceDoubleBlockSourceOperator(LongStream.range(0, size).mapToDouble(l -> ESTestCase.randomDouble())); } diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/MaxDoubleGroupingAggregatorFunctionTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/MaxDoubleGroupingAggregatorFunctionTests.java index 3750aec95f3a7..9a2c8bc17685d 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/MaxDoubleGroupingAggregatorFunctionTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/MaxDoubleGroupingAggregatorFunctionTests.java @@ -9,6 +9,7 @@ import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.BlockFactory; import org.elasticsearch.compute.data.DoubleBlock; import org.elasticsearch.compute.data.Page; import org.elasticsearch.compute.operator.LongDoubleTupleBlockSourceOperator; @@ -24,7 +25,7 @@ public class MaxDoubleGroupingAggregatorFunctionTests extends GroupingAggregatorFunctionTestCase { @Override - protected SourceOperator simpleInput(int end) { + protected SourceOperator simpleInput(BlockFactory blockFactory, int end) { return new LongDoubleTupleBlockSourceOperator( LongStream.range(0, end).mapToObj(l -> Tuple.tuple(randomLongBetween(0, 4), randomDouble())) ); diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/MaxIntAggregatorFunctionTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/MaxIntAggregatorFunctionTests.java index 72cfa06222b50..e76021b883120 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/MaxIntAggregatorFunctionTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/MaxIntAggregatorFunctionTests.java @@ -9,6 +9,7 @@ import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.BlockFactory; import org.elasticsearch.compute.data.IntBlock; import org.elasticsearch.compute.operator.SequenceIntBlockSourceOperator; import org.elasticsearch.compute.operator.SourceOperator; @@ -20,7 +21,7 @@ public class MaxIntAggregatorFunctionTests extends AggregatorFunctionTestCase { @Override - protected SourceOperator simpleInput(int size) { + protected SourceOperator simpleInput(BlockFactory blockFactory, int size) { return new SequenceIntBlockSourceOperator(IntStream.range(0, size).map(l -> randomInt())); } diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/MaxIntGroupingAggregatorFunctionTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/MaxIntGroupingAggregatorFunctionTests.java index 9ffee498eeba2..313e10be39855 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/MaxIntGroupingAggregatorFunctionTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/MaxIntGroupingAggregatorFunctionTests.java @@ -9,6 +9,7 @@ import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.BlockFactory; import org.elasticsearch.compute.data.IntBlock; import org.elasticsearch.compute.data.Page; import org.elasticsearch.compute.operator.LongIntBlockSourceOperator; @@ -33,7 +34,7 @@ protected String expectedDescriptionOfAggregator() { } @Override - protected SourceOperator simpleInput(int size) { + protected SourceOperator simpleInput(BlockFactory blockFactory, int size) { return new LongIntBlockSourceOperator(LongStream.range(0, size).mapToObj(l -> Tuple.tuple(randomLongBetween(0, 4), randomInt()))); } diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/MaxLongAggregatorFunctionTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/MaxLongAggregatorFunctionTests.java index 4e84f2e672b97..a51aa98f7a5a8 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/MaxLongAggregatorFunctionTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/MaxLongAggregatorFunctionTests.java @@ -9,6 +9,7 @@ import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.BlockFactory; import org.elasticsearch.compute.data.LongBlock; import org.elasticsearch.compute.operator.SequenceLongBlockSourceOperator; import org.elasticsearch.compute.operator.SourceOperator; @@ -20,9 +21,9 @@ public class MaxLongAggregatorFunctionTests extends AggregatorFunctionTestCase { @Override - protected SourceOperator simpleInput(int size) { + protected SourceOperator simpleInput(BlockFactory blockFactory, int size) { long max = randomLongBetween(1, Long.MAX_VALUE / size); - return new SequenceLongBlockSourceOperator(LongStream.range(0, size).map(l -> randomLongBetween(-max, max))); + return new SequenceLongBlockSourceOperator(blockFactory, LongStream.range(0, size).map(l -> randomLongBetween(-max, max))); } @Override diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/MaxLongGroupingAggregatorFunctionTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/MaxLongGroupingAggregatorFunctionTests.java index e284f2a6103d1..a1f44e128c2e1 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/MaxLongGroupingAggregatorFunctionTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/MaxLongGroupingAggregatorFunctionTests.java @@ -9,6 +9,7 @@ import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.BlockFactory; import org.elasticsearch.compute.data.LongBlock; import org.elasticsearch.compute.data.Page; import org.elasticsearch.compute.operator.SourceOperator; @@ -33,8 +34,11 @@ protected String expectedDescriptionOfAggregator() { } @Override - protected SourceOperator simpleInput(int size) { - return new TupleBlockSourceOperator(LongStream.range(0, size).mapToObj(l -> Tuple.tuple(randomLongBetween(0, 4), randomLong()))); + protected SourceOperator simpleInput(BlockFactory blockFactory, int size) { + return new TupleBlockSourceOperator( + blockFactory, + LongStream.range(0, size).mapToObj(l -> Tuple.tuple(randomLongBetween(0, 4), randomLong())) + ); } @Override diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/MedianAbsoluteDeviationDoubleAggregatorFunctionTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/MedianAbsoluteDeviationDoubleAggregatorFunctionTests.java index 74bda421a545e..1c14a8e7855ce 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/MedianAbsoluteDeviationDoubleAggregatorFunctionTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/MedianAbsoluteDeviationDoubleAggregatorFunctionTests.java @@ -10,6 +10,7 @@ import org.elasticsearch.common.Randomness; import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.BlockFactory; import org.elasticsearch.compute.data.DoubleBlock; import org.elasticsearch.compute.operator.SequenceDoubleBlockSourceOperator; import org.elasticsearch.compute.operator.SourceOperator; @@ -22,7 +23,7 @@ public class MedianAbsoluteDeviationDoubleAggregatorFunctionTests extends AggregatorFunctionTestCase { @Override - protected SourceOperator simpleInput(int end) { + protected SourceOperator simpleInput(BlockFactory blockFactory, int end) { List values = Arrays.asList(1.2, 1.25, 2.0, 2.0, 4.3, 6.0, 9.0); Randomness.shuffle(values); return new SequenceDoubleBlockSourceOperator(values); diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/MedianAbsoluteDeviationDoubleGroupingAggregatorFunctionTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/MedianAbsoluteDeviationDoubleGroupingAggregatorFunctionTests.java index 6751486453f30..06ddb2a734f8c 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/MedianAbsoluteDeviationDoubleGroupingAggregatorFunctionTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/MedianAbsoluteDeviationDoubleGroupingAggregatorFunctionTests.java @@ -10,6 +10,7 @@ import org.elasticsearch.common.Randomness; import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.BlockFactory; import org.elasticsearch.compute.data.DoubleBlock; import org.elasticsearch.compute.data.Page; import org.elasticsearch.compute.operator.LongDoubleTupleBlockSourceOperator; @@ -27,7 +28,7 @@ public class MedianAbsoluteDeviationDoubleGroupingAggregatorFunctionTests extends GroupingAggregatorFunctionTestCase { @Override - protected SourceOperator simpleInput(int end) { + protected SourceOperator simpleInput(BlockFactory blockFactory, int end) { double[][] samples = new double[][] { { 1.2, 1.25, 2.0, 2.0, 4.3, 6.0, 9.0 }, { 0.1, 1.5, 2.0, 3.0, 4.0, 7.5, 100.0 }, diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/MedianAbsoluteDeviationIntAggregatorFunctionTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/MedianAbsoluteDeviationIntAggregatorFunctionTests.java index 20506cc5c8f93..40e422b6efc26 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/MedianAbsoluteDeviationIntAggregatorFunctionTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/MedianAbsoluteDeviationIntAggregatorFunctionTests.java @@ -10,6 +10,7 @@ import org.elasticsearch.common.Randomness; import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.BlockFactory; import org.elasticsearch.compute.data.DoubleBlock; import org.elasticsearch.compute.operator.SequenceIntBlockSourceOperator; import org.elasticsearch.compute.operator.SourceOperator; @@ -22,7 +23,7 @@ public class MedianAbsoluteDeviationIntAggregatorFunctionTests extends AggregatorFunctionTestCase { @Override - protected SourceOperator simpleInput(int end) { + protected SourceOperator simpleInput(BlockFactory blockFactory, int end) { List values = Arrays.asList(12, 125, 20, 20, 43, 60, 90); Randomness.shuffle(values); return new SequenceIntBlockSourceOperator(values); diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/MedianAbsoluteDeviationIntGroupingAggregatorFunctionTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/MedianAbsoluteDeviationIntGroupingAggregatorFunctionTests.java index 20f62c67a16cc..2f00764f6fe51 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/MedianAbsoluteDeviationIntGroupingAggregatorFunctionTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/MedianAbsoluteDeviationIntGroupingAggregatorFunctionTests.java @@ -10,6 +10,7 @@ import org.elasticsearch.common.Randomness; import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.BlockFactory; import org.elasticsearch.compute.data.DoubleBlock; import org.elasticsearch.compute.data.Page; import org.elasticsearch.compute.operator.LongIntBlockSourceOperator; @@ -27,7 +28,7 @@ public class MedianAbsoluteDeviationIntGroupingAggregatorFunctionTests extends GroupingAggregatorFunctionTestCase { @Override - protected SourceOperator simpleInput(int end) { + protected SourceOperator simpleInput(BlockFactory blockFactory, int end) { int[][] samples = new int[][] { { 12, 125, 20, 20, 43, 60, 90 }, { 1, 15, 20, 30, 40, 75, 1000 }, diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/MedianAbsoluteDeviationLongAggregatorFunctionTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/MedianAbsoluteDeviationLongAggregatorFunctionTests.java index d80415f83daa2..465bb5800bbb6 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/MedianAbsoluteDeviationLongAggregatorFunctionTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/MedianAbsoluteDeviationLongAggregatorFunctionTests.java @@ -10,6 +10,7 @@ import org.elasticsearch.common.Randomness; import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.BlockFactory; import org.elasticsearch.compute.data.DoubleBlock; import org.elasticsearch.compute.operator.SequenceLongBlockSourceOperator; import org.elasticsearch.compute.operator.SourceOperator; @@ -22,10 +23,10 @@ public class MedianAbsoluteDeviationLongAggregatorFunctionTests extends AggregatorFunctionTestCase { @Override - protected SourceOperator simpleInput(int end) { + protected SourceOperator simpleInput(BlockFactory blockFactory, int end) { List values = Arrays.asList(12L, 125L, 20L, 20L, 43L, 60L, 90L); Randomness.shuffle(values); - return new SequenceLongBlockSourceOperator(values); + return new SequenceLongBlockSourceOperator(blockFactory, values); } @Override diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/MedianAbsoluteDeviationLongGroupingAggregatorFunctionTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/MedianAbsoluteDeviationLongGroupingAggregatorFunctionTests.java index c3cebad8e0e0b..2c6bfc1204591 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/MedianAbsoluteDeviationLongGroupingAggregatorFunctionTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/MedianAbsoluteDeviationLongGroupingAggregatorFunctionTests.java @@ -10,6 +10,7 @@ import org.elasticsearch.common.Randomness; import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.BlockFactory; import org.elasticsearch.compute.data.DoubleBlock; import org.elasticsearch.compute.data.Page; import org.elasticsearch.compute.operator.SourceOperator; @@ -27,7 +28,7 @@ public class MedianAbsoluteDeviationLongGroupingAggregatorFunctionTests extends GroupingAggregatorFunctionTestCase { @Override - protected SourceOperator simpleInput(int end) { + protected SourceOperator simpleInput(BlockFactory blockFactory, int end) { long[][] samples = new long[][] { { 12, 125, 20, 20, 43, 60, 90 }, { 1, 15, 20, 30, 40, 75, 1000 }, @@ -42,7 +43,7 @@ protected SourceOperator simpleInput(int end) { values.add(Tuple.tuple((long) i, v)); } } - return new TupleBlockSourceOperator(values); + return new TupleBlockSourceOperator(blockFactory, values); } @Override diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/MinDoubleAggregatorFunctionTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/MinDoubleAggregatorFunctionTests.java index 622302d549fd0..7e0b7241cf258 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/MinDoubleAggregatorFunctionTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/MinDoubleAggregatorFunctionTests.java @@ -9,6 +9,7 @@ import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.BlockFactory; import org.elasticsearch.compute.data.DoubleBlock; import org.elasticsearch.compute.operator.SequenceDoubleBlockSourceOperator; import org.elasticsearch.compute.operator.SourceOperator; @@ -21,7 +22,7 @@ public class MinDoubleAggregatorFunctionTests extends AggregatorFunctionTestCase { @Override - protected SourceOperator simpleInput(int size) { + protected SourceOperator simpleInput(BlockFactory blockFactory, int size) { return new SequenceDoubleBlockSourceOperator(LongStream.range(0, size).mapToDouble(l -> ESTestCase.randomDouble())); } diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/MinDoubleGroupingAggregatorFunctionTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/MinDoubleGroupingAggregatorFunctionTests.java index 12c63e354547a..7c4141f4a7ad1 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/MinDoubleGroupingAggregatorFunctionTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/MinDoubleGroupingAggregatorFunctionTests.java @@ -9,6 +9,7 @@ import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.BlockFactory; import org.elasticsearch.compute.data.DoubleBlock; import org.elasticsearch.compute.data.Page; import org.elasticsearch.compute.operator.LongDoubleTupleBlockSourceOperator; @@ -23,7 +24,7 @@ public class MinDoubleGroupingAggregatorFunctionTests extends GroupingAggregatorFunctionTestCase { @Override - protected SourceOperator simpleInput(int end) { + protected SourceOperator simpleInput(BlockFactory blockFactory, int end) { return new LongDoubleTupleBlockSourceOperator( LongStream.range(0, end).mapToObj(l -> Tuple.tuple(randomLongBetween(0, 4), randomDouble())) ); diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/MinIntAggregatorFunctionTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/MinIntAggregatorFunctionTests.java index 2dc0e893875ab..dc1ab1398fb90 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/MinIntAggregatorFunctionTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/MinIntAggregatorFunctionTests.java @@ -9,6 +9,7 @@ import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.BlockFactory; import org.elasticsearch.compute.data.IntBlock; import org.elasticsearch.compute.operator.SequenceIntBlockSourceOperator; import org.elasticsearch.compute.operator.SourceOperator; @@ -20,7 +21,7 @@ public class MinIntAggregatorFunctionTests extends AggregatorFunctionTestCase { @Override - protected SourceOperator simpleInput(int size) { + protected SourceOperator simpleInput(BlockFactory blockFactory, int size) { return new SequenceIntBlockSourceOperator(IntStream.range(0, size).map(l -> randomInt())); } diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/MinIntGroupingAggregatorFunctionTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/MinIntGroupingAggregatorFunctionTests.java index 4ffbe9b1396d3..55cfc2d124e5f 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/MinIntGroupingAggregatorFunctionTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/MinIntGroupingAggregatorFunctionTests.java @@ -9,6 +9,7 @@ import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.BlockFactory; import org.elasticsearch.compute.data.IntBlock; import org.elasticsearch.compute.data.Page; import org.elasticsearch.compute.operator.LongIntBlockSourceOperator; @@ -33,7 +34,7 @@ protected String expectedDescriptionOfAggregator() { } @Override - protected SourceOperator simpleInput(int size) { + protected SourceOperator simpleInput(BlockFactory blockFactory, int size) { return new LongIntBlockSourceOperator(LongStream.range(0, size).mapToObj(l -> Tuple.tuple(randomLongBetween(0, 4), randomInt()))); } diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/MinLongAggregatorFunctionTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/MinLongAggregatorFunctionTests.java index 25a420237893e..91feb141ac74b 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/MinLongAggregatorFunctionTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/MinLongAggregatorFunctionTests.java @@ -9,6 +9,7 @@ import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.BlockFactory; import org.elasticsearch.compute.data.LongBlock; import org.elasticsearch.compute.operator.SequenceLongBlockSourceOperator; import org.elasticsearch.compute.operator.SourceOperator; @@ -20,9 +21,9 @@ public class MinLongAggregatorFunctionTests extends AggregatorFunctionTestCase { @Override - protected SourceOperator simpleInput(int size) { + protected SourceOperator simpleInput(BlockFactory blockFactory, int size) { long max = randomLongBetween(1, Long.MAX_VALUE / size); - return new SequenceLongBlockSourceOperator(LongStream.range(0, size).map(l -> randomLongBetween(-max, max))); + return new SequenceLongBlockSourceOperator(blockFactory, LongStream.range(0, size).map(l -> randomLongBetween(-max, max))); } @Override diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/MinLongGroupingAggregatorFunctionTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/MinLongGroupingAggregatorFunctionTests.java index 311e7e41ed9ac..02dda3fe3c236 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/MinLongGroupingAggregatorFunctionTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/MinLongGroupingAggregatorFunctionTests.java @@ -9,6 +9,7 @@ import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.BlockFactory; import org.elasticsearch.compute.data.LongBlock; import org.elasticsearch.compute.data.Page; import org.elasticsearch.compute.operator.SourceOperator; @@ -33,8 +34,11 @@ protected String expectedDescriptionOfAggregator() { } @Override - protected SourceOperator simpleInput(int size) { - return new TupleBlockSourceOperator(LongStream.range(0, size).mapToObj(l -> Tuple.tuple(randomLongBetween(0, 4), randomLong()))); + protected SourceOperator simpleInput(BlockFactory blockFactory, int size) { + return new TupleBlockSourceOperator( + blockFactory, + LongStream.range(0, size).mapToObj(l -> Tuple.tuple(randomLongBetween(0, 4), randomLong())) + ); } @Override diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/PercentileDoubleAggregatorFunctionTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/PercentileDoubleAggregatorFunctionTests.java index 96e61d4782022..61f26cd0209b3 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/PercentileDoubleAggregatorFunctionTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/PercentileDoubleAggregatorFunctionTests.java @@ -9,6 +9,7 @@ import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.BlockFactory; import org.elasticsearch.compute.data.DoubleBlock; import org.elasticsearch.compute.operator.SequenceDoubleBlockSourceOperator; import org.elasticsearch.compute.operator.SourceOperator; @@ -41,7 +42,7 @@ protected String expectedDescriptionOfAggregator() { } @Override - protected SourceOperator simpleInput(int size) { + protected SourceOperator simpleInput(BlockFactory blockFactory, int size) { return new SequenceDoubleBlockSourceOperator(LongStream.range(0, size).mapToDouble(l -> ESTestCase.randomDouble())); } diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/PercentileDoubleGroupingAggregatorFunctionTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/PercentileDoubleGroupingAggregatorFunctionTests.java index c0d6595e088eb..9495e78ec47ca 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/PercentileDoubleGroupingAggregatorFunctionTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/PercentileDoubleGroupingAggregatorFunctionTests.java @@ -9,6 +9,7 @@ import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.BlockFactory; import org.elasticsearch.compute.data.DoubleBlock; import org.elasticsearch.compute.data.Page; import org.elasticsearch.compute.operator.LongDoubleTupleBlockSourceOperator; @@ -42,7 +43,7 @@ protected String expectedDescriptionOfAggregator() { } @Override - protected SourceOperator simpleInput(int end) { + protected SourceOperator simpleInput(BlockFactory blockFactory, int end) { return new LongDoubleTupleBlockSourceOperator( LongStream.range(0, end).mapToObj(l -> Tuple.tuple(randomLongBetween(0, 4), randomDouble())) ); diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/PercentileIntAggregatorFunctionTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/PercentileIntAggregatorFunctionTests.java index c34a01e608d1a..37d153f7bcae6 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/PercentileIntAggregatorFunctionTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/PercentileIntAggregatorFunctionTests.java @@ -9,6 +9,7 @@ import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.BlockFactory; import org.elasticsearch.compute.data.DoubleBlock; import org.elasticsearch.compute.operator.SequenceIntBlockSourceOperator; import org.elasticsearch.compute.operator.SourceOperator; @@ -40,7 +41,7 @@ protected String expectedDescriptionOfAggregator() { } @Override - protected SourceOperator simpleInput(int size) { + protected SourceOperator simpleInput(BlockFactory blockFactory, int size) { int max = between(1, (int) Math.min(Integer.MAX_VALUE, Long.MAX_VALUE / size)); return new SequenceIntBlockSourceOperator(LongStream.range(0, size).mapToInt(l -> between(0, max))); } diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/PercentileIntGroupingAggregatorFunctionTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/PercentileIntGroupingAggregatorFunctionTests.java index a018fba96e897..948e156e52c85 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/PercentileIntGroupingAggregatorFunctionTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/PercentileIntGroupingAggregatorFunctionTests.java @@ -9,6 +9,7 @@ import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.BlockFactory; import org.elasticsearch.compute.data.DoubleBlock; import org.elasticsearch.compute.data.Page; import org.elasticsearch.compute.operator.LongIntBlockSourceOperator; @@ -42,7 +43,7 @@ protected String expectedDescriptionOfAggregator() { } @Override - protected SourceOperator simpleInput(int size) { + protected SourceOperator simpleInput(BlockFactory blockFactory, int size) { int max = between(1, (int) Math.min(Integer.MAX_VALUE, Long.MAX_VALUE / size)); return new LongIntBlockSourceOperator( LongStream.range(0, size).mapToObj(l -> Tuple.tuple(randomLongBetween(0, 4), between(-1, max))) diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/PercentileLongAggregatorFunctionTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/PercentileLongAggregatorFunctionTests.java index cf0b18840d91e..eb32dac18ea80 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/PercentileLongAggregatorFunctionTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/PercentileLongAggregatorFunctionTests.java @@ -9,6 +9,7 @@ import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.BlockFactory; import org.elasticsearch.compute.data.DoubleBlock; import org.elasticsearch.compute.operator.SequenceLongBlockSourceOperator; import org.elasticsearch.compute.operator.SourceOperator; @@ -40,9 +41,9 @@ protected String expectedDescriptionOfAggregator() { } @Override - protected SourceOperator simpleInput(int size) { + protected SourceOperator simpleInput(BlockFactory blockFactory, int size) { long max = randomLongBetween(1, 1_000_000); - return new SequenceLongBlockSourceOperator(LongStream.range(0, size).map(l -> randomLongBetween(0, max))); + return new SequenceLongBlockSourceOperator(blockFactory, LongStream.range(0, size).map(l -> randomLongBetween(0, max))); } @Override diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/PercentileLongGroupingAggregatorFunctionTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/PercentileLongGroupingAggregatorFunctionTests.java index 609526532b72e..6360be8595ff8 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/PercentileLongGroupingAggregatorFunctionTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/PercentileLongGroupingAggregatorFunctionTests.java @@ -9,6 +9,7 @@ import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.BlockFactory; import org.elasticsearch.compute.data.DoubleBlock; import org.elasticsearch.compute.data.Page; import org.elasticsearch.compute.operator.SourceOperator; @@ -42,9 +43,10 @@ protected String expectedDescriptionOfAggregator() { } @Override - protected SourceOperator simpleInput(int size) { + protected SourceOperator simpleInput(BlockFactory blockFactory, int size) { long max = randomLongBetween(1, Long.MAX_VALUE / size / 5); return new TupleBlockSourceOperator( + blockFactory, LongStream.range(0, size).mapToObj(l -> Tuple.tuple(randomLongBetween(0, 4), randomLongBetween(-0, max))) ); } diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/SumDoubleAggregatorFunctionTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/SumDoubleAggregatorFunctionTests.java index 767f9a2d5c25b..d3dc262419008 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/SumDoubleAggregatorFunctionTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/SumDoubleAggregatorFunctionTests.java @@ -9,6 +9,7 @@ import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.BlockFactory; import org.elasticsearch.compute.data.DoubleBlock; import org.elasticsearch.compute.data.Page; import org.elasticsearch.compute.operator.Driver; @@ -28,7 +29,7 @@ public class SumDoubleAggregatorFunctionTests extends AggregatorFunctionTestCase { @Override - protected SourceOperator simpleInput(int size) { + protected SourceOperator simpleInput(BlockFactory blockFactory, int size) { return new SequenceDoubleBlockSourceOperator(LongStream.range(0, size).mapToDouble(l -> ESTestCase.randomDouble())); } diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/SumDoubleGroupingAggregatorFunctionTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/SumDoubleGroupingAggregatorFunctionTests.java index 03a7269b84690..8b86d99653282 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/SumDoubleGroupingAggregatorFunctionTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/SumDoubleGroupingAggregatorFunctionTests.java @@ -9,6 +9,7 @@ import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.BlockFactory; import org.elasticsearch.compute.data.DoubleBlock; import org.elasticsearch.compute.data.Page; import org.elasticsearch.compute.operator.LongDoubleTupleBlockSourceOperator; @@ -23,7 +24,7 @@ public class SumDoubleGroupingAggregatorFunctionTests extends GroupingAggregatorFunctionTestCase { @Override - protected SourceOperator simpleInput(int end) { + protected SourceOperator simpleInput(BlockFactory blockFactory, int end) { return new LongDoubleTupleBlockSourceOperator( LongStream.range(0, end).mapToObj(l -> Tuple.tuple(randomLongBetween(0, 4), randomDouble())) ); diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/SumIntAggregatorFunctionTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/SumIntAggregatorFunctionTests.java index e6fccf2d46f61..736386fae3dec 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/SumIntAggregatorFunctionTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/SumIntAggregatorFunctionTests.java @@ -27,7 +27,7 @@ public class SumIntAggregatorFunctionTests extends AggregatorFunctionTestCase { @Override - protected SourceOperator simpleInput(int size) { + protected SourceOperator simpleInput(BlockFactory blockFactory, int size) { int max = between(1, (int) Math.min(Integer.MAX_VALUE, Long.MAX_VALUE / size)); return new SequenceIntBlockSourceOperator(LongStream.range(0, size).mapToInt(l -> between(-max, max))); } diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/SumIntGroupingAggregatorFunctionTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/SumIntGroupingAggregatorFunctionTests.java index 71666024c819d..0b8678a0e3f05 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/SumIntGroupingAggregatorFunctionTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/SumIntGroupingAggregatorFunctionTests.java @@ -9,6 +9,7 @@ import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.BlockFactory; import org.elasticsearch.compute.data.LongBlock; import org.elasticsearch.compute.data.Page; import org.elasticsearch.compute.operator.LongIntBlockSourceOperator; @@ -32,7 +33,7 @@ protected String expectedDescriptionOfAggregator() { } @Override - protected SourceOperator simpleInput(int size) { + protected SourceOperator simpleInput(BlockFactory blockFactory, int size) { int max = between(1, (int) Math.min(Integer.MAX_VALUE, Long.MAX_VALUE / size)); return new LongIntBlockSourceOperator( LongStream.range(0, size).mapToObj(l -> Tuple.tuple(randomLongBetween(0, 4), between(-max, max))) diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/SumLongAggregatorFunctionTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/SumLongAggregatorFunctionTests.java index ae5aaa5b21965..e9523c5583cd4 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/SumLongAggregatorFunctionTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/SumLongAggregatorFunctionTests.java @@ -27,9 +27,9 @@ public class SumLongAggregatorFunctionTests extends AggregatorFunctionTestCase { @Override - protected SourceOperator simpleInput(int size) { + protected SourceOperator simpleInput(BlockFactory blockFactory, int size) { long max = randomLongBetween(1, Long.MAX_VALUE / size); - return new SequenceLongBlockSourceOperator(LongStream.range(0, size).map(l -> randomLongBetween(-max, max))); + return new SequenceLongBlockSourceOperator(blockFactory, LongStream.range(0, size).map(l -> randomLongBetween(-max, max))); } @Override @@ -53,7 +53,7 @@ public void testOverflowFails() { try ( Driver d = new Driver( driverContext, - new SequenceLongBlockSourceOperator(LongStream.of(Long.MAX_VALUE - 1, 2)), + new SequenceLongBlockSourceOperator(driverContext.blockFactory(), LongStream.of(Long.MAX_VALUE - 1, 2)), List.of(simple(nonBreakingBigArrays()).get(driverContext)), new PageConsumerOperator(page -> fail("shouldn't have made it this far")), () -> {} diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/SumLongGroupingAggregatorFunctionTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/SumLongGroupingAggregatorFunctionTests.java index e0dc918b515d6..827dc06a4f542 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/SumLongGroupingAggregatorFunctionTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/aggregation/SumLongGroupingAggregatorFunctionTests.java @@ -9,6 +9,7 @@ import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.BlockFactory; import org.elasticsearch.compute.data.LongBlock; import org.elasticsearch.compute.data.Page; import org.elasticsearch.compute.operator.SourceOperator; @@ -32,9 +33,10 @@ protected String expectedDescriptionOfAggregator() { } @Override - protected SourceOperator simpleInput(int size) { + protected SourceOperator simpleInput(BlockFactory blockFactory, int size) { long max = randomLongBetween(1, Long.MAX_VALUE / size / 5); return new TupleBlockSourceOperator( + blockFactory, LongStream.range(0, size).mapToObj(l -> Tuple.tuple(randomLongBetween(0, 4), randomLongBetween(-max, max))) ); } diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/BlockBuilderTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/BlockBuilderTests.java index de552d242afa2..2179e68c47832 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/BlockBuilderTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/BlockBuilderTests.java @@ -7,47 +7,48 @@ package org.elasticsearch.compute.data; +import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; + +import org.elasticsearch.common.breaker.CircuitBreaker; +import org.elasticsearch.common.breaker.CircuitBreakingException; +import org.elasticsearch.common.unit.ByteSizeValue; +import org.elasticsearch.common.util.BigArrays; +import org.elasticsearch.common.util.MockBigArrays; +import org.elasticsearch.common.util.PageCacheRecycler; +import org.elasticsearch.indices.CrankyCircuitBreakerService; import org.elasticsearch.test.ESTestCase; +import java.util.ArrayList; import java.util.List; +import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; public class BlockBuilderTests extends ESTestCase { - - public void testAllNullsInt() { - for (int numEntries : List.of(1, randomIntBetween(1, 100))) { - testAllNullsImpl(IntBlock.newBlockBuilder(0), numEntries); - testAllNullsImpl(IntBlock.newBlockBuilder(100), numEntries); - testAllNullsImpl(IntBlock.newBlockBuilder(1000), numEntries); - testAllNullsImpl(IntBlock.newBlockBuilder(randomIntBetween(0, 100)), numEntries); + @ParametersFactory + public static List params() { + List params = new ArrayList<>(); + for (ElementType elementType : ElementType.values()) { + if (elementType == ElementType.UNKNOWN || elementType == ElementType.NULL || elementType == ElementType.DOC) { + continue; + } + params.add(new Object[] { elementType }); } + return params; } - public void testAllNullsLong() { - for (int numEntries : List.of(1, randomIntBetween(1, 100))) { - testAllNullsImpl(LongBlock.newBlockBuilder(0), numEntries); - testAllNullsImpl(LongBlock.newBlockBuilder(100), numEntries); - testAllNullsImpl(LongBlock.newBlockBuilder(1000), numEntries); - testAllNullsImpl(LongBlock.newBlockBuilder(randomIntBetween(0, 100)), numEntries); - } - } + private final ElementType elementType; - public void testAllNullsDouble() { - for (int numEntries : List.of(1, randomIntBetween(1, 100))) { - testAllNullsImpl(DoubleBlock.newBlockBuilder(0), numEntries); - testAllNullsImpl(DoubleBlock.newBlockBuilder(100), numEntries); - testAllNullsImpl(DoubleBlock.newBlockBuilder(1000), numEntries); - testAllNullsImpl(DoubleBlock.newBlockBuilder(randomIntBetween(0, 100)), numEntries); - } + public BlockBuilderTests(ElementType elementType) { + this.elementType = elementType; } - public void testAllNullsBytesRef() { + public void testAllNulls() { for (int numEntries : List.of(1, randomIntBetween(1, 100))) { - testAllNullsImpl(BytesRefBlock.newBlockBuilder(0), numEntries); - testAllNullsImpl(BytesRefBlock.newBlockBuilder(100), numEntries); - testAllNullsImpl(BytesRefBlock.newBlockBuilder(1000), numEntries); - testAllNullsImpl(BytesRefBlock.newBlockBuilder(randomIntBetween(0, 100)), numEntries); + testAllNullsImpl(elementType.newBlockBuilder(0), numEntries); + testAllNullsImpl(elementType.newBlockBuilder(100), numEntries); + testAllNullsImpl(elementType.newBlockBuilder(1000), numEntries); + testAllNullsImpl(elementType.newBlockBuilder(randomIntBetween(0, 100)), numEntries); } } @@ -65,4 +66,95 @@ private void testAllNullsImpl(Block.Builder builder, int numEntries) { static int randomPosition(int positionCount) { return positionCount == 1 ? 0 : randomIntBetween(0, positionCount - 1); } + + public void testCloseWithoutBuilding() { + BlockFactory blockFactory = BlockFactoryTests.blockFactory(ByteSizeValue.ofGb(1)); + elementType.newBlockBuilder(10, blockFactory).close(); + assertThat(blockFactory.breaker().getUsed(), equalTo(0L)); + } + + public void testBuildSmallSingleValued() { + testBuild(between(1, 100), false, 1); + } + + public void testBuildHugeSingleValued() { + testBuild(between(1_000, 50_000), false, 1); + } + + public void testBuildSmallSingleValuedNullable() { + testBuild(between(1, 100), true, 1); + } + + public void testBuildHugeSingleValuedNullable() { + testBuild(between(1_000, 50_000), true, 1); + } + + public void testBuildSmallMultiValued() { + testBuild(between(1, 100), false, 3); + } + + public void testBuildHugeMultiValued() { + testBuild(between(1_000, 50_000), false, 3); + } + + public void testBuildSmallMultiValuedNullable() { + testBuild(between(1, 100), true, 3); + } + + public void testBuildHugeMultiValuedNullable() { + testBuild(between(1_000, 50_000), true, 3); + } + + public void testBuildSingle() { + testBuild(1, false, 1); + } + + private void testBuild(int size, boolean nullable, int maxValueCount) { + BlockFactory blockFactory = BlockFactoryTests.blockFactory(ByteSizeValue.ofGb(1)); + try (Block.Builder builder = elementType.newBlockBuilder(randomBoolean() ? size : 1, blockFactory)) { + BasicBlockTests.RandomBlock random = BasicBlockTests.randomBlock(elementType, size, nullable, 1, maxValueCount, 0, 0); + builder.copyFrom(random.block(), 0, random.block().getPositionCount()); + try (Block built = builder.build()) { + assertThat(built, equalTo(random.block())); + assertThat(blockFactory.breaker().getUsed(), equalTo(built.ramBytesUsed())); + } + assertThat(blockFactory.breaker().getUsed(), equalTo(0L)); + } + assertThat(blockFactory.breaker().getUsed(), equalTo(0L)); + } + + public void testDoubleBuild() { + BlockFactory blockFactory = BlockFactoryTests.blockFactory(ByteSizeValue.ofGb(1)); + try (Block.Builder builder = elementType.newBlockBuilder(10, blockFactory)) { + BasicBlockTests.RandomBlock random = BasicBlockTests.randomBlock(elementType, 10, false, 1, 1, 0, 0); + builder.copyFrom(random.block(), 0, random.block().getPositionCount()); + try (Block built = builder.build()) { + assertThat(built, equalTo(random.block())); + assertThat(blockFactory.breaker().getUsed(), equalTo(built.ramBytesUsed())); + } + assertThat(blockFactory.breaker().getUsed(), equalTo(0L)); + Exception e = expectThrows(IllegalStateException.class, builder::build); + assertThat(e.getMessage(), equalTo("already closed")); + } + assertThat(blockFactory.breaker().getUsed(), equalTo(0L)); + } + + public void testCranky() { + BigArrays bigArrays = new MockBigArrays(PageCacheRecycler.NON_RECYCLING_INSTANCE, new CrankyCircuitBreakerService()); + BlockFactory blockFactory = new BlockFactory(bigArrays.breakerService().getBreaker(CircuitBreaker.REQUEST), bigArrays); + try { + try (Block.Builder builder = elementType.newBlockBuilder(10, blockFactory)) { + BasicBlockTests.RandomBlock random = BasicBlockTests.randomBlock(elementType, 10, false, 1, 1, 0, 0); + builder.copyFrom(random.block(), 0, random.block().getPositionCount()); + try (Block built = builder.build()) { + assertThat(built, equalTo(random.block())); + } + } + // If we made it this far cranky didn't fail us! + } catch (CircuitBreakingException e) { + logger.info("cranky", e); + assertThat(e.getMessage(), equalTo(CrankyCircuitBreakerService.ERROR_MESSAGE)); + } + assertThat(blockFactory.breaker().getUsed(), equalTo(0L)); + } } diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/BlockFactoryTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/BlockFactoryTests.java index a524221dd50d7..24d4e27e92ad1 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/BlockFactoryTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/BlockFactoryTests.java @@ -49,11 +49,29 @@ public static BlockFactory blockFactory(ByteSizeValue size) { @ParametersFactory public static List params() { - List> l = List.of(() -> { - CircuitBreaker breaker = new MockBigArrays.LimitedBreaker("esql-test-breaker", ByteSizeValue.ofGb(1)); - BigArrays bigArrays = new MockBigArrays(PageCacheRecycler.NON_RECYCLING_INSTANCE, mockBreakerService(breaker)); - return BlockFactory.getInstance(breaker, bigArrays); - }, BlockFactory::getGlobalInstance); + List> l = List.of(new Supplier<>() { + @Override + public BlockFactory get() { + CircuitBreaker breaker = new MockBigArrays.LimitedBreaker("esql-test-breaker", ByteSizeValue.ofGb(1)); + BigArrays bigArrays = new MockBigArrays(PageCacheRecycler.NON_RECYCLING_INSTANCE, mockBreakerService(breaker)); + return BlockFactory.getInstance(breaker, bigArrays); + } + + @Override + public String toString() { + return "1gb"; + } + }, new Supplier<>() { + @Override + public BlockFactory get() { + return BlockFactory.getGlobalInstance(); + } + + @Override + public String toString() { + return "global"; + } + }); return l.stream().map(s -> new Object[] { s }).toList(); } diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/BlockTestUtils.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/BlockTestUtils.java index a5637128705ca..dd61c8f6478d3 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/BlockTestUtils.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/BlockTestUtils.java @@ -77,6 +77,7 @@ public static void readInto(List> values, Page page) { for (int i = 0; i < page.getBlockCount(); i++) { readInto(values.get(i), page.getBlock(i)); } + page.releaseBlocks(); } public static void readInto(List values, Block block) { diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/BytesRefBlockEqualityTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/BytesRefBlockEqualityTests.java index 0eb9beec2e7f9..ee654497c1ec3 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/BytesRefBlockEqualityTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/BytesRefBlockEqualityTests.java @@ -18,7 +18,6 @@ import java.util.Arrays; import java.util.BitSet; import java.util.List; -import java.util.stream.IntStream; public class BytesRefBlockEqualityTests extends ESTestCase { @@ -332,10 +331,14 @@ public void testSimpleBlockWithSingleNull() { public void testSimpleBlockWithManyNulls() { int positions = randomIntBetween(1, 256); boolean grow = randomBoolean(); - var builder = BytesRefBlock.newBlockBuilder(grow ? 0 : positions); - IntStream.range(0, positions).forEach(i -> builder.appendNull()); - BytesRefBlock block1 = builder.build(); - BytesRefBlock block2 = builder.build(); + BytesRefBlock.Builder builder1 = BytesRefBlock.newBlockBuilder(grow ? 0 : positions); + BytesRefBlock.Builder builder2 = BytesRefBlock.newBlockBuilder(grow ? 0 : positions); + for (int p = 0; p < positions; p++) { + builder1.appendNull(); + builder2.appendNull(); + } + BytesRefBlock block1 = builder1.build(); + BytesRefBlock block2 = builder2.build(); assertEquals(positions, block1.getPositionCount()); assertTrue(block1.mayHaveNulls()); assertTrue(block1.isNull(0)); @@ -365,15 +368,27 @@ public void testSimpleBlockWithSingleMultiValue() { public void testSimpleBlockWithManyMultiValues() { int positions = randomIntBetween(1, 256); boolean grow = randomBoolean(); - var builder = BytesRefBlock.newBlockBuilder(grow ? 0 : positions); + BytesRefBlock.Builder builder1 = BytesRefBlock.newBlockBuilder(grow ? 0 : positions); + BytesRefBlock.Builder builder2 = BytesRefBlock.newBlockBuilder(grow ? 0 : positions); + BytesRefBlock.Builder builder3 = BytesRefBlock.newBlockBuilder(grow ? 0 : positions); for (int pos = 0; pos < positions; pos++) { - builder.beginPositionEntry(); + builder1.beginPositionEntry(); + builder2.beginPositionEntry(); + builder3.beginPositionEntry(); int values = randomIntBetween(1, 16); - IntStream.range(0, values).forEach(i -> builder.appendBytesRef(new BytesRef(Integer.toHexString(randomInt())))); + for (int i = 0; i < values; i++) { + BytesRef value = new BytesRef(Integer.toHexString(randomInt())); + builder1.appendBytesRef(value); + builder2.appendBytesRef(value); + builder3.appendBytesRef(value); + } + builder1.endPositionEntry(); + builder2.endPositionEntry(); + builder3.endPositionEntry(); } - BytesRefBlock block1 = builder.build(); - BytesRefBlock block2 = builder.build(); - BytesRefBlock block3 = builder.build(); + BytesRefBlock block1 = builder1.build(); + BytesRefBlock block2 = builder2.build(); + BytesRefBlock block3 = builder3.build(); assertEquals(positions, block1.getPositionCount()); assertAllEquals(List.of(block1, block2, block3)); diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/DocVectorTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/DocVectorTests.java index d8258ab28a078..0f76e024c7436 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/DocVectorTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/DocVectorTests.java @@ -8,6 +8,7 @@ package org.elasticsearch.compute.data; import org.elasticsearch.common.Randomness; +import org.elasticsearch.common.unit.ByteSizeValue; import org.elasticsearch.core.Releasables; import org.elasticsearch.test.ESTestCase; @@ -98,33 +99,37 @@ public void testRandomShardSegmentDocMap() { } private void assertShardSegmentDocMap(int[][] data, int[][] expected) { - DocBlock.Builder builder = DocBlock.newBlockBuilder(data.length); - for (int r = 0; r < data.length; r++) { - builder.appendShard(data[r][0]); - builder.appendSegment(data[r][1]); - builder.appendDoc(data[r][2]); + BlockFactory blockFactory = BlockFactoryTests.blockFactory(ByteSizeValue.ofGb(1)); + try (DocBlock.Builder builder = DocBlock.newBlockBuilder(data.length, blockFactory)) { + for (int r = 0; r < data.length; r++) { + builder.appendShard(data[r][0]); + builder.appendSegment(data[r][1]); + builder.appendDoc(data[r][2]); + } + try (DocVector docVector = builder.build().asVector()) { + int[] forwards = docVector.shardSegmentDocMapForwards(); + + int[][] result = new int[docVector.getPositionCount()][]; + for (int p = 0; p < result.length; p++) { + result[p] = new int[] { + docVector.shards().getInt(forwards[p]), + docVector.segments().getInt(forwards[p]), + docVector.docs().getInt(forwards[p]) }; + } + assertThat(result, equalTo(expected)); + + int[] backwards = docVector.shardSegmentDocMapBackwards(); + for (int p = 0; p < result.length; p++) { + result[p] = new int[] { + docVector.shards().getInt(backwards[forwards[p]]), + docVector.segments().getInt(backwards[forwards[p]]), + docVector.docs().getInt(backwards[forwards[p]]) }; + } + + assertThat(result, equalTo(data)); + } } - DocVector docVector = builder.build().asVector(); - int[] forwards = docVector.shardSegmentDocMapForwards(); - - int[][] result = new int[docVector.getPositionCount()][]; - for (int p = 0; p < result.length; p++) { - result[p] = new int[] { - docVector.shards().getInt(forwards[p]), - docVector.segments().getInt(forwards[p]), - docVector.docs().getInt(forwards[p]) }; - } - assertThat(result, equalTo(expected)); - - int[] backwards = docVector.shardSegmentDocMapBackwards(); - for (int p = 0; p < result.length; p++) { - result[p] = new int[] { - docVector.shards().getInt(backwards[forwards[p]]), - docVector.segments().getInt(backwards[forwards[p]]), - docVector.docs().getInt(backwards[forwards[p]]) }; - } - - assertThat(result, equalTo(data)); + assertThat(blockFactory.breaker().getUsed(), equalTo(0L)); } public void testCannotDoubleRelease() { diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/DoubleBlockEqualityTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/DoubleBlockEqualityTests.java index 2abbcc0b989f1..7dda97f52834e 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/DoubleBlockEqualityTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/DoubleBlockEqualityTests.java @@ -11,7 +11,6 @@ import java.util.BitSet; import java.util.List; -import java.util.stream.IntStream; public class DoubleBlockEqualityTests extends ESTestCase { @@ -224,10 +223,14 @@ public void testSimpleBlockWithSingleNull() { public void testSimpleBlockWithManyNulls() { int positions = randomIntBetween(1, 256); boolean grow = randomBoolean(); - var builder = DoubleBlock.newBlockBuilder(grow ? 0 : positions); - IntStream.range(0, positions).forEach(i -> builder.appendNull()); - DoubleBlock block1 = builder.build(); - DoubleBlock block2 = builder.build(); + DoubleBlock.Builder builder1 = DoubleBlock.newBlockBuilder(grow ? 0 : positions); + DoubleBlock.Builder builder2 = DoubleBlock.newBlockBuilder(grow ? 0 : positions); + for (int p = 0; p < positions; p++) { + builder1.appendNull(); + builder2.appendNull(); + } + DoubleBlock block1 = builder1.build(); + DoubleBlock block2 = builder2.build(); assertEquals(positions, block1.getPositionCount()); assertTrue(block1.mayHaveNulls()); assertTrue(block1.isNull(0)); @@ -248,15 +251,27 @@ public void testSimpleBlockWithSingleMultiValue() { public void testSimpleBlockWithManyMultiValues() { int positions = randomIntBetween(1, 256); boolean grow = randomBoolean(); - var builder = DoubleBlock.newBlockBuilder(grow ? 0 : positions); + DoubleBlock.Builder builder1 = DoubleBlock.newBlockBuilder(grow ? 0 : positions); + DoubleBlock.Builder builder2 = DoubleBlock.newBlockBuilder(grow ? 0 : positions); + DoubleBlock.Builder builder3 = DoubleBlock.newBlockBuilder(grow ? 0 : positions); for (int pos = 0; pos < positions; pos++) { - builder.beginPositionEntry(); + builder1.beginPositionEntry(); + builder2.beginPositionEntry(); + builder3.beginPositionEntry(); int values = randomIntBetween(1, 16); - IntStream.range(0, values).forEach(i -> builder.appendDouble(randomDouble())); + for (int i = 0; i < values; i++) { + double value = randomDouble(); + builder1.appendDouble(value); + builder2.appendDouble(value); + builder3.appendDouble(value); + } + builder1.endPositionEntry(); + builder2.endPositionEntry(); + builder3.endPositionEntry(); } - DoubleBlock block1 = builder.build(); - DoubleBlock block2 = builder.build(); - DoubleBlock block3 = builder.build(); + DoubleBlock block1 = builder1.build(); + DoubleBlock block2 = builder2.build(); + DoubleBlock block3 = builder3.build(); assertEquals(positions, block1.getPositionCount()); assertAllEquals(List.of(block1, block2, block3)); diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/IntBlockEqualityTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/IntBlockEqualityTests.java index c4e19106d4368..40c84324f13d2 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/IntBlockEqualityTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/IntBlockEqualityTests.java @@ -11,7 +11,6 @@ import java.util.BitSet; import java.util.List; -import java.util.stream.IntStream; public class IntBlockEqualityTests extends ESTestCase { @@ -185,10 +184,14 @@ public void testSimpleBlockWithSingleNull() { public void testSimpleBlockWithManyNulls() { int positions = randomIntBetween(1, 256); boolean grow = randomBoolean(); - var builder = IntBlock.newBlockBuilder(grow ? 0 : positions); - IntStream.range(0, positions).forEach(i -> builder.appendNull()); - IntBlock block1 = builder.build(); - IntBlock block2 = builder.build(); + IntBlock.Builder builder1 = IntBlock.newBlockBuilder(grow ? 0 : positions); + IntBlock.Builder builder2 = IntBlock.newBlockBuilder(grow ? 0 : positions); + for (int p = 0; p < positions; p++) { + builder1.appendNull(); + builder2.appendNull(); + } + IntBlock block1 = builder1.build(); + IntBlock block2 = builder2.build(); assertEquals(positions, block1.getPositionCount()); assertTrue(block1.mayHaveNulls()); assertTrue(block1.isNull(0)); @@ -210,15 +213,27 @@ public void testSimpleBlockWithSingleMultiValue() { public void testSimpleBlockWithManyMultiValues() { int positions = randomIntBetween(1, 256); boolean grow = randomBoolean(); - var builder = IntBlock.newBlockBuilder(grow ? 0 : positions); + IntBlock.Builder builder1 = IntBlock.newBlockBuilder(grow ? 0 : positions); + IntBlock.Builder builder2 = IntBlock.newBlockBuilder(grow ? 0 : positions); + IntBlock.Builder builder3 = IntBlock.newBlockBuilder(grow ? 0 : positions); for (int pos = 0; pos < positions; pos++) { - builder.beginPositionEntry(); + builder1.beginPositionEntry(); + builder2.beginPositionEntry(); + builder3.beginPositionEntry(); int values = randomIntBetween(1, 16); - IntStream.range(0, values).forEach(i -> builder.appendInt(randomInt())); + for (int i = 0; i < values; i++) { + int value = randomInt(); + builder1.appendInt(value); + builder2.appendInt(value); + builder3.appendInt(value); + } + builder1.endPositionEntry(); + builder2.endPositionEntry(); + builder3.endPositionEntry(); } - IntBlock block1 = builder.build(); - IntBlock block2 = builder.build(); - IntBlock block3 = builder.build(); + IntBlock block1 = builder1.build(); + IntBlock block2 = builder2.build(); + IntBlock block3 = builder3.build(); assertEquals(positions, block1.getPositionCount()); assertAllEquals(List.of(block1, block2, block3)); diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/LongBlockEqualityTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/LongBlockEqualityTests.java index 3d08b2a96d635..a24b4a4dd6fa6 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/LongBlockEqualityTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/LongBlockEqualityTests.java @@ -11,7 +11,6 @@ import java.util.BitSet; import java.util.List; -import java.util.stream.IntStream; public class LongBlockEqualityTests extends ESTestCase { @@ -191,10 +190,14 @@ public void testSimpleBlockWithSingleNull() { public void testSimpleBlockWithManyNulls() { int positions = randomIntBetween(1, 256); boolean grow = randomBoolean(); - var builder = LongBlock.newBlockBuilder(grow ? 0 : positions); - IntStream.range(0, positions).forEach(i -> builder.appendNull()); - LongBlock block1 = builder.build(); - LongBlock block2 = builder.build(); + LongBlock.Builder builder1 = LongBlock.newBlockBuilder(grow ? 0 : positions); + LongBlock.Builder builder2 = LongBlock.newBlockBuilder(grow ? 0 : positions); + for (int p = 0; p < positions; p++) { + builder1.appendNull(); + builder2.appendNull(); + } + LongBlock block1 = builder1.build(); + LongBlock block2 = builder2.build(); assertEquals(positions, block1.getPositionCount()); assertTrue(block1.mayHaveNulls()); assertTrue(block1.isNull(0)); @@ -216,15 +219,27 @@ public void testSimpleBlockWithSingleMultiValue() { public void testSimpleBlockWithManyMultiValues() { int positions = randomIntBetween(1, 256); boolean grow = randomBoolean(); - var builder = LongBlock.newBlockBuilder(grow ? 0 : positions); + LongBlock.Builder builder1 = LongBlock.newBlockBuilder(grow ? 0 : positions); + LongBlock.Builder builder2 = LongBlock.newBlockBuilder(grow ? 0 : positions); + LongBlock.Builder builder3 = LongBlock.newBlockBuilder(grow ? 0 : positions); for (int pos = 0; pos < positions; pos++) { - builder.beginPositionEntry(); - int values = randomIntBetween(1, 16); - IntStream.range(0, values).forEach(i -> builder.appendLong(randomLong())); + builder1.beginPositionEntry(); + builder2.beginPositionEntry(); + builder3.beginPositionEntry(); + int valueCount = randomIntBetween(1, 16); + for (int i = 0; i < valueCount; i++) { + long value = randomLong(); + builder1.appendLong(value); + builder2.appendLong(value); + builder3.appendLong(value); + } + builder1.endPositionEntry(); + builder2.endPositionEntry(); + builder3.endPositionEntry(); } - LongBlock block1 = builder.build(); - LongBlock block2 = builder.build(); - LongBlock block3 = builder.build(); + LongBlock block1 = builder1.build(); + LongBlock block2 = builder2.build(); + LongBlock block3 = builder3.build(); assertEquals(positions, block1.getPositionCount()); assertAllEquals(List.of(block1, block2, block3)); diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/TestBlockBuilder.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/TestBlockBuilder.java index 4684da93a661a..d9377a490368d 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/TestBlockBuilder.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/TestBlockBuilder.java @@ -139,6 +139,11 @@ public Block.Builder appendAllValuesToCurrentPosition(Block block) { public IntBlock build() { return builder.build(); } + + @Override + public void close() { + builder.close(); + } } private static class TestLongBlockBuilder extends TestBlockBuilder { @@ -195,6 +200,11 @@ public Block.Builder appendAllValuesToCurrentPosition(Block block) { public LongBlock build() { return builder.build(); } + + @Override + public void close() { + builder.close(); + } } private static class TestDoubleBlockBuilder extends TestBlockBuilder { @@ -251,6 +261,11 @@ public Block.Builder appendAllValuesToCurrentPosition(Block block) { public DoubleBlock build() { return builder.build(); } + + @Override + public void close() { + builder.close(); + } } private static class TestBytesRefBlockBuilder extends TestBlockBuilder { @@ -307,6 +322,11 @@ public Block.Builder appendAllValuesToCurrentPosition(Block block) { public BytesRefBlock build() { return builder.build(); } + + @Override + public void close() { + builder.close(); + } } private static class TestBooleanBlockBuilder extends TestBlockBuilder { @@ -366,5 +386,10 @@ public Block.Builder appendAllValuesToCurrentPosition(Block block) { public BooleanBlock build() { return builder.build(); } + + @Override + public void close() { + builder.close(); + } } } diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/VectorBuilderTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/VectorBuilderTests.java new file mode 100644 index 0000000000000..656d79070f217 --- /dev/null +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/VectorBuilderTests.java @@ -0,0 +1,153 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.compute.data; + +import com.carrotsearch.randomizedtesting.annotations.ParametersFactory; + +import org.apache.lucene.util.BytesRef; +import org.elasticsearch.common.breaker.CircuitBreaker; +import org.elasticsearch.common.breaker.CircuitBreakingException; +import org.elasticsearch.common.unit.ByteSizeValue; +import org.elasticsearch.common.util.BigArrays; +import org.elasticsearch.common.util.MockBigArrays; +import org.elasticsearch.common.util.PageCacheRecycler; +import org.elasticsearch.indices.CrankyCircuitBreakerService; +import org.elasticsearch.test.ESTestCase; + +import java.util.ArrayList; +import java.util.List; + +import static org.hamcrest.Matchers.equalTo; + +public class VectorBuilderTests extends ESTestCase { + @ParametersFactory + public static List params() { + List params = new ArrayList<>(); + for (ElementType elementType : ElementType.values()) { + if (elementType == ElementType.UNKNOWN || elementType == ElementType.NULL || elementType == ElementType.DOC) { + continue; + } + params.add(new Object[] { elementType }); + } + return params; + } + + private final ElementType elementType; + + public VectorBuilderTests(ElementType elementType) { + this.elementType = elementType; + } + + public void testCloseWithoutBuilding() { + BlockFactory blockFactory = BlockFactoryTests.blockFactory(ByteSizeValue.ofGb(1)); + vectorBuilder(10, blockFactory).close(); + assertThat(blockFactory.breaker().getUsed(), equalTo(0L)); + } + + public void testBuildSmall() { + testBuild(between(1, 100)); + } + + public void testBuildHuge() { + testBuild(between(1_000, 50_000)); + } + + public void testBuildSingle() { + testBuild(1); + } + + private void testBuild(int size) { + BlockFactory blockFactory = BlockFactoryTests.blockFactory(ByteSizeValue.ofGb(1)); + try (Vector.Builder builder = vectorBuilder(randomBoolean() ? size : 1, blockFactory)) { + BasicBlockTests.RandomBlock random = BasicBlockTests.randomBlock(elementType, size, false, 1, 1, 0, 0); + fill(builder, random.block().asVector()); + try (Vector built = builder.build()) { + assertThat(built, equalTo(random.block().asVector())); + assertThat(blockFactory.breaker().getUsed(), equalTo(built.ramBytesUsed())); + } + assertThat(blockFactory.breaker().getUsed(), equalTo(0L)); + } + assertThat(blockFactory.breaker().getUsed(), equalTo(0L)); + } + + public void testDoubleBuild() { + BlockFactory blockFactory = BlockFactoryTests.blockFactory(ByteSizeValue.ofGb(1)); + try (Vector.Builder builder = vectorBuilder(10, blockFactory)) { + BasicBlockTests.RandomBlock random = BasicBlockTests.randomBlock(elementType, 10, false, 1, 1, 0, 0); + fill(builder, random.block().asVector()); + try (Vector built = builder.build()) { + assertThat(built, equalTo(random.block().asVector())); + } + assertThat(blockFactory.breaker().getUsed(), equalTo(0L)); + Exception e = expectThrows(IllegalStateException.class, builder::build); + assertThat(e.getMessage(), equalTo("already closed")); + } + assertThat(blockFactory.breaker().getUsed(), equalTo(0L)); + } + + public void testCranky() { + BigArrays bigArrays = new MockBigArrays(PageCacheRecycler.NON_RECYCLING_INSTANCE, new CrankyCircuitBreakerService()); + BlockFactory blockFactory = new BlockFactory(bigArrays.breakerService().getBreaker(CircuitBreaker.REQUEST), bigArrays); + try { + try (Vector.Builder builder = vectorBuilder(10, blockFactory)) { + BasicBlockTests.RandomBlock random = BasicBlockTests.randomBlock(elementType, 10, false, 1, 1, 0, 0); + fill(builder, random.block().asVector()); + try (Vector built = builder.build()) { + assertThat(built, equalTo(random.block().asVector())); + } + } + // If we made it this far cranky didn't fail us! + } catch (CircuitBreakingException e) { + logger.info("cranky", e); + assertThat(e.getMessage(), equalTo(CrankyCircuitBreakerService.ERROR_MESSAGE)); + } + assertThat(blockFactory.breaker().getUsed(), equalTo(0L)); + } + + private Vector.Builder vectorBuilder(int estimatedSize, BlockFactory blockFactory) { + return switch (elementType) { + case NULL, DOC, UNKNOWN -> throw new UnsupportedOperationException(); + case BOOLEAN -> BooleanVector.newVectorBuilder(estimatedSize, blockFactory); + case BYTES_REF -> BytesRefVector.newVectorBuilder(estimatedSize, blockFactory); + case DOUBLE -> DoubleVector.newVectorBuilder(estimatedSize, blockFactory); + case INT -> IntVector.newVectorBuilder(estimatedSize, blockFactory); + case LONG -> LongVector.newVectorBuilder(estimatedSize, blockFactory); + }; + } + + private void fill(Vector.Builder builder, Vector from) { + switch (elementType) { + case NULL, DOC, UNKNOWN -> throw new UnsupportedOperationException(); + case BOOLEAN -> { + for (int p = 0; p < from.getPositionCount(); p++) { + ((BooleanVector.Builder) builder).appendBoolean(((BooleanVector) from).getBoolean(p)); + } + } + case BYTES_REF -> { + for (int p = 0; p < from.getPositionCount(); p++) { + ((BytesRefVector.Builder) builder).appendBytesRef(((BytesRefVector) from).getBytesRef(p, new BytesRef())); + } + } + case DOUBLE -> { + for (int p = 0; p < from.getPositionCount(); p++) { + ((DoubleVector.Builder) builder).appendDouble(((DoubleVector) from).getDouble(p)); + } + } + case INT -> { + for (int p = 0; p < from.getPositionCount(); p++) { + ((IntVector.Builder) builder).appendInt(((IntVector) from).getInt(p)); + } + } + case LONG -> { + for (int p = 0; p < from.getPositionCount(); p++) { + ((LongVector.Builder) builder).appendLong(((LongVector) from).getLong(p)); + } + } + } + } +} diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/VectorFixedBuilderTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/VectorFixedBuilderTests.java index 9fa9f7e32c654..df67ee2e7822a 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/VectorFixedBuilderTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/VectorFixedBuilderTests.java @@ -45,6 +45,12 @@ public VectorFixedBuilderTests(ElementType elementType) { this.elementType = elementType; } + public void testCloseWithoutBuilding() { + BlockFactory blockFactory = BlockFactoryTests.blockFactory(ByteSizeValue.ofGb(1)); + vectorBuilder(10, blockFactory).close(); + assertThat(blockFactory.breaker().getUsed(), equalTo(0L)); + } + public void testBuildSmall() { testBuild(between(1, 100)); } @@ -59,25 +65,32 @@ public void testBuildSingle() { private void testBuild(int size) { BlockFactory blockFactory = BlockFactoryTests.blockFactory(ByteSizeValue.ofGb(1)); - Vector.Builder builder = vectorBuilder(size, blockFactory); - BasicBlockTests.RandomBlock random = BasicBlockTests.randomBlock(elementType, size, false, 1, 1, 0, 0); - fill(builder, random.block().asVector()); - try (Vector built = builder.build()) { - assertThat(built, equalTo(random.block().asVector())); - assertThat(blockFactory.breaker().getUsed(), equalTo(built.ramBytesUsed())); + try (Vector.Builder builder = vectorBuilder(size, blockFactory)) { + BasicBlockTests.RandomBlock random = BasicBlockTests.randomBlock(elementType, size, false, 1, 1, 0, 0); + fill(builder, random.block().asVector()); + try (Vector built = builder.build()) { + assertThat(built, equalTo(random.block().asVector())); + assertThat(blockFactory.breaker().getUsed(), equalTo(built.ramBytesUsed())); + } + assertThat(blockFactory.breaker().getUsed(), equalTo(0L)); } + assertThat(blockFactory.breaker().getUsed(), equalTo(0L)); } public void testDoubleBuild() { BlockFactory blockFactory = BlockFactoryTests.blockFactory(ByteSizeValue.ofGb(1)); - Vector.Builder builder = vectorBuilder(10, blockFactory); - BasicBlockTests.RandomBlock random = BasicBlockTests.randomBlock(elementType, 10, false, 1, 1, 0, 0); - fill(builder, random.block().asVector()); - try (Vector built = builder.build()) { - assertThat(built, equalTo(random.block().asVector())); + try (Vector.Builder builder = vectorBuilder(10, blockFactory)) { + BasicBlockTests.RandomBlock random = BasicBlockTests.randomBlock(elementType, 10, false, 1, 1, 0, 0); + fill(builder, random.block().asVector()); + try (Vector built = builder.build()) { + assertThat(built, equalTo(random.block().asVector())); + } + assertThat(blockFactory.breaker().getUsed(), equalTo(0L)); + Exception e = expectThrows(IllegalStateException.class, builder::build); + assertThat(e.getMessage(), equalTo("already closed")); } - Exception e = expectThrows(IllegalStateException.class, builder::build); - assertThat(e.getMessage(), equalTo("already closed")); + assertThat(blockFactory.breaker().getUsed(), equalTo(0L)); + } public void testCranky() { diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/lucene/ValuesSourceReaderOperatorTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/lucene/ValuesSourceReaderOperatorTests.java index 64edcaa43d89b..4776b40e12115 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/lucene/ValuesSourceReaderOperatorTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/lucene/ValuesSourceReaderOperatorTests.java @@ -26,6 +26,7 @@ import org.elasticsearch.common.unit.ByteSizeValue; import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.BlockFactory; import org.elasticsearch.compute.data.BooleanBlock; import org.elasticsearch.compute.data.BooleanVector; import org.elasticsearch.compute.data.BytesRefBlock; @@ -115,7 +116,7 @@ static Operator.OperatorFactory factory(IndexReader reader, ValuesSourceType vsT } @Override - protected SourceOperator simpleInput(int size) { + protected SourceOperator simpleInput(BlockFactory blockFactory, int size) { // The test wants more than one segment. We shoot for about 10. int commitEvery = Math.max(1, size / 10); try ( @@ -198,21 +199,35 @@ protected ByteSizeValue smallEnoughToCircuitBreak() { } public void testLoadAll() { - loadSimpleAndAssert(CannedSourceOperator.collectPages(simpleInput(between(1_000, 100 * 1024)))); + DriverContext driverContext = driverContext(); + loadSimpleAndAssert( + driverContext, + CannedSourceOperator.collectPages(simpleInput(driverContext.blockFactory(), between(1_000, 100 * 1024))) + ); } public void testLoadAllInOnePage() { + DriverContext driverContext = driverContext(); loadSimpleAndAssert( - List.of(CannedSourceOperator.mergePages(CannedSourceOperator.collectPages(simpleInput(between(1_000, 100 * 1024))))) + driverContext, + List.of( + CannedSourceOperator.mergePages( + CannedSourceOperator.collectPages(simpleInput(driverContext.blockFactory(), between(1_000, 100 * 1024))) + ) + ) ); } public void testEmpty() { - loadSimpleAndAssert(CannedSourceOperator.collectPages(simpleInput(0))); + DriverContext driverContext = driverContext(); + loadSimpleAndAssert(driverContext, CannedSourceOperator.collectPages(simpleInput(driverContext.blockFactory(), 0))); } public void testLoadAllInOnePageShuffled() { - Page source = CannedSourceOperator.mergePages(CannedSourceOperator.collectPages(simpleInput(between(1_000, 100 * 1024)))); + DriverContext driverContext = driverContext(); + Page source = CannedSourceOperator.mergePages( + CannedSourceOperator.collectPages(simpleInput(driverContext.blockFactory(), between(1_000, 100 * 1024))) + ); List shuffleList = new ArrayList<>(); IntStream.range(0, source.getPositionCount()).forEach(i -> shuffleList.add(i)); Randomness.shuffle(shuffleList); @@ -222,11 +237,10 @@ public void testLoadAllInOnePageShuffled() { shuffledBlocks[b] = source.getBlock(b).filter(shuffleArray); } source = new Page(shuffledBlocks); - loadSimpleAndAssert(List.of(source)); + loadSimpleAndAssert(driverContext, List.of(source)); } - private void loadSimpleAndAssert(List input) { - DriverContext driverContext = driverContext(); + private void loadSimpleAndAssert(DriverContext driverContext, List input) { List results = new ArrayList<>(); List operators = List.of( factory( diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/AggregationOperatorTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/AggregationOperatorTests.java index 9eaa1e333f66e..784d5134e9608 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/AggregationOperatorTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/AggregationOperatorTests.java @@ -17,6 +17,7 @@ import org.elasticsearch.compute.aggregation.SumLongAggregatorFunctionSupplier; import org.elasticsearch.compute.aggregation.SumLongAggregatorFunctionTests; import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.BlockFactory; import org.elasticsearch.compute.data.Page; import java.util.List; @@ -28,9 +29,9 @@ public class AggregationOperatorTests extends ForkingOperatorTestCase { @Override - protected SourceOperator simpleInput(int size) { + protected SourceOperator simpleInput(BlockFactory blockFactory, int size) { long max = randomLongBetween(1, Long.MAX_VALUE / size); - return new SequenceLongBlockSourceOperator(LongStream.range(0, size).map(l -> randomLongBetween(-max, max))); + return new SequenceLongBlockSourceOperator(blockFactory, LongStream.range(0, size).map(l -> randomLongBetween(-max, max))); } @Override diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/AnyOperatorTestCase.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/AnyOperatorTestCase.java index 8f995d9a31bc3..14d83ce252e5f 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/AnyOperatorTestCase.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/AnyOperatorTestCase.java @@ -95,7 +95,7 @@ protected final BigArrays nonBreakingBigArrays() { /** * A {@link DriverContext} with a nonBreakingBigArrays. */ - protected DriverContext driverContext() { + protected DriverContext driverContext() { // TODO make this final and return a breaking block factory return new DriverContext(nonBreakingBigArrays(), BlockFactory.getNonBreakingInstance()); } } diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/CannedSourceOperator.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/CannedSourceOperator.java index d5b07a713b8b4..57ea313b88dab 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/CannedSourceOperator.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/CannedSourceOperator.java @@ -53,6 +53,25 @@ public static Page mergePages(List pages) { return new Page(blocks); } + /** + * Make a deep copy of some pages. Useful so that when the originals are + * released the copies are still live. + */ + public static List deepCopyOf(List pages) { + List out = new ArrayList<>(pages.size()); + for (Page p : pages) { + Block[] blocks = new Block[p.getBlockCount()]; + for (int b = 0; b < blocks.length; b++) { + Block orig = p.getBlock(b); + Block.Builder builder = orig.elementType().newBlockBuilder(p.getPositionCount()); + builder.copyFrom(orig, 0, p.getPositionCount()); + blocks[b] = builder.build(); + } + out.add(new Page(blocks)); + } + return out; + } + private final Iterator page; public CannedSourceOperator(Iterator page) { @@ -77,5 +96,9 @@ public Page getOutput() { } @Override - public void close() {} + public void close() { + while (page.hasNext()) { + page.next().releaseBlocks(); + } + } } diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/ColumnExtractOperatorTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/ColumnExtractOperatorTests.java index 7825e035df0db..f06a2780b7446 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/ColumnExtractOperatorTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/ColumnExtractOperatorTests.java @@ -12,6 +12,7 @@ import org.elasticsearch.common.unit.ByteSizeValue; import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.BlockFactory; import org.elasticsearch.compute.data.BytesRefBlock; import org.elasticsearch.compute.data.ElementType; import org.elasticsearch.compute.data.Page; @@ -24,7 +25,7 @@ public class ColumnExtractOperatorTests extends OperatorTestCase { @Override - protected SourceOperator simpleInput(int end) { + protected SourceOperator simpleInput(BlockFactory blockFactory, int end) { List input = LongStream.range(0, end) .mapToObj(l -> new BytesRef("word1_" + l + " word2_" + l + " word3_" + l)) .collect(Collectors.toList()); diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/EvalOperatorTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/EvalOperatorTests.java index 156f37d8d8e7a..91e18214abee2 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/EvalOperatorTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/EvalOperatorTests.java @@ -10,6 +10,7 @@ import org.elasticsearch.common.unit.ByteSizeValue; import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.BlockFactory; import org.elasticsearch.compute.data.LongBlock; import org.elasticsearch.compute.data.LongVector; import org.elasticsearch.compute.data.Page; @@ -21,8 +22,8 @@ public class EvalOperatorTests extends OperatorTestCase { @Override - protected SourceOperator simpleInput(int end) { - return new TupleBlockSourceOperator(LongStream.range(0, end).mapToObj(l -> Tuple.tuple(l, end - l))); + protected SourceOperator simpleInput(BlockFactory blockFactory, int end) { + return new TupleBlockSourceOperator(blockFactory, LongStream.range(0, end).mapToObj(l -> Tuple.tuple(l, end - l))); } record Addition(int lhs, int rhs) implements EvalOperator.ExpressionEvaluator { diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/FilterOperatorTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/FilterOperatorTests.java index b26fe0c33fe1c..9c29471473203 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/FilterOperatorTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/FilterOperatorTests.java @@ -10,6 +10,7 @@ import org.elasticsearch.common.unit.ByteSizeValue; import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.BlockFactory; import org.elasticsearch.compute.data.BooleanVector; import org.elasticsearch.compute.data.LongBlock; import org.elasticsearch.compute.data.LongVector; @@ -23,8 +24,8 @@ public class FilterOperatorTests extends OperatorTestCase { @Override - protected SourceOperator simpleInput(int end) { - return new TupleBlockSourceOperator(LongStream.range(0, end).mapToObj(l -> Tuple.tuple(l, end - l))); + protected SourceOperator simpleInput(BlockFactory blockFactory, int end) { + return new TupleBlockSourceOperator(blockFactory, LongStream.range(0, end).mapToObj(l -> Tuple.tuple(l, end - l))); } record SameLastDigit(int lhs, int rhs) implements EvalOperator.ExpressionEvaluator { diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/ForkingOperatorTestCase.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/ForkingOperatorTestCase.java index 9d1084fcc4cf3..d01a5b17ac788 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/ForkingOperatorTestCase.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/ForkingOperatorTestCase.java @@ -56,7 +56,7 @@ protected final Operator.OperatorFactory simple(BigArrays bigArrays) { public final void testInitialFinal() { BigArrays bigArrays = nonBreakingBigArrays(); DriverContext driverContext = driverContext(); - List input = CannedSourceOperator.collectPages(simpleInput(between(1_000, 100_000))); + List input = CannedSourceOperator.collectPages(simpleInput(driverContext.blockFactory(), between(1_000, 100_000))); List results = new ArrayList<>(); try ( @@ -80,7 +80,7 @@ public final void testInitialFinal() { public final void testManyInitialFinal() { BigArrays bigArrays = nonBreakingBigArrays(); DriverContext driverContext = driverContext(); - List input = CannedSourceOperator.collectPages(simpleInput(between(1_000, 100_000))); + List input = CannedSourceOperator.collectPages(simpleInput(driverContext.blockFactory(), between(1_000, 100_000))); List partials = oneDriverPerPage(input, () -> List.of(simpleWithMode(bigArrays, AggregatorMode.INITIAL).get(driverContext))); List results = new ArrayList<>(); try ( @@ -101,7 +101,7 @@ public final void testManyInitialFinal() { public final void testInitialIntermediateFinal() { BigArrays bigArrays = nonBreakingBigArrays(); DriverContext driverContext = driverContext(); - List input = CannedSourceOperator.collectPages(simpleInput(between(1_000, 100_000))); + List input = CannedSourceOperator.collectPages(simpleInput(driverContext.blockFactory(), between(1_000, 100_000))); List results = new ArrayList<>(); try ( @@ -127,7 +127,7 @@ public final void testInitialIntermediateFinal() { public final void testManyInitialManyPartialFinal() { BigArrays bigArrays = nonBreakingBigArrays(); DriverContext driverContext = driverContext(); - List input = CannedSourceOperator.collectPages(simpleInput(between(1_000, 100_000))); + List input = CannedSourceOperator.collectPages(simpleInput(driverContext.blockFactory(), between(1_000, 100_000))); List partials = oneDriverPerPage(input, () -> List.of(simpleWithMode(bigArrays, AggregatorMode.INITIAL).get(driverContext))); Collections.shuffle(partials, random()); @@ -156,7 +156,7 @@ public final void testManyInitialManyPartialFinal() { // to move the data through the pipeline. public final void testManyInitialManyPartialFinalRunner() { BigArrays bigArrays = nonBreakingBigArrays(); - List input = CannedSourceOperator.collectPages(simpleInput(between(1_000, 100_000))); + List input = CannedSourceOperator.collectPages(simpleInput(driverContext().blockFactory(), between(1_000, 100_000))); List results = new ArrayList<>(); List drivers = createDriversForInput(bigArrays, input, results, false /* no throwing ops */); @@ -178,7 +178,7 @@ protected void start(Driver driver, ActionListener listener) { // runner behaves correctly and also releases all resources (bigArrays) appropriately. public final void testManyInitialManyPartialFinalRunnerThrowing() { BigArrays bigArrays = nonBreakingBigArrays(); - List input = CannedSourceOperator.collectPages(simpleInput(between(1_000, 100_000))); + List input = CannedSourceOperator.collectPages(simpleInput(driverContext().blockFactory(), between(1_000, 100_000))); List results = new ArrayList<>(); List drivers = createDriversForInput(bigArrays, input, results, true /* one throwing op */); diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/HashAggregationOperatorTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/HashAggregationOperatorTests.java index 954a1f179f259..1afa5d3c02330 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/HashAggregationOperatorTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/HashAggregationOperatorTests.java @@ -17,6 +17,7 @@ import org.elasticsearch.compute.aggregation.SumLongAggregatorFunctionSupplier; import org.elasticsearch.compute.aggregation.SumLongGroupingAggregatorFunctionTests; import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.BlockFactory; import org.elasticsearch.compute.data.ElementType; import org.elasticsearch.compute.data.LongBlock; import org.elasticsearch.compute.data.Page; @@ -31,9 +32,12 @@ public class HashAggregationOperatorTests extends ForkingOperatorTestCase { @Override - protected SourceOperator simpleInput(int size) { + protected SourceOperator simpleInput(BlockFactory blockFactory, int size) { long max = randomLongBetween(1, Long.MAX_VALUE / size); - return new TupleBlockSourceOperator(LongStream.range(0, size).mapToObj(l -> Tuple.tuple(l % 5, randomLongBetween(-max, max)))); + return new TupleBlockSourceOperator( + blockFactory, + LongStream.range(0, size).mapToObj(l -> Tuple.tuple(l % 5, randomLongBetween(-max, max))) + ); } @Override diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/LimitOperatorTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/LimitOperatorTests.java index 228fdf262cf62..bbbfd44014ffc 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/LimitOperatorTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/LimitOperatorTests.java @@ -10,6 +10,7 @@ import org.elasticsearch.common.unit.ByteSizeValue; import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.BlockFactory; import org.elasticsearch.compute.data.Page; import java.util.List; @@ -24,8 +25,8 @@ protected Operator.OperatorFactory simple(BigArrays bigArrays) { } @Override - protected SourceOperator simpleInput(int size) { - return new SequenceLongBlockSourceOperator(LongStream.range(0, size)); + protected SourceOperator simpleInput(BlockFactory blockFactory, int size) { + return new SequenceLongBlockSourceOperator(blockFactory, LongStream.range(0, size)); } @Override diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/MvExpandOperatorTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/MvExpandOperatorTests.java index 80ac57ed539e7..21ca59e0f45a4 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/MvExpandOperatorTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/MvExpandOperatorTests.java @@ -10,6 +10,7 @@ import org.elasticsearch.common.unit.ByteSizeValue; import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.compute.data.BasicBlockTests; +import org.elasticsearch.compute.data.BlockFactory; import org.elasticsearch.compute.data.ElementType; import org.elasticsearch.compute.data.IntBlock; import org.elasticsearch.compute.data.IntVector; @@ -24,7 +25,7 @@ public class MvExpandOperatorTests extends OperatorTestCase { @Override - protected SourceOperator simpleInput(int end) { + protected SourceOperator simpleInput(BlockFactory blockFactory, int end) { return new AbstractBlockSourceOperator(8 * 1024) { private int idx; diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/OperatorTestCase.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/OperatorTestCase.java index 3b2fac5271aa6..aee600b079b81 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/OperatorTestCase.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/OperatorTestCase.java @@ -12,6 +12,7 @@ import org.elasticsearch.common.Randomness; import org.elasticsearch.common.breaker.CircuitBreaker; import org.elasticsearch.common.breaker.CircuitBreakingException; +import org.elasticsearch.common.collect.Iterators; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.ByteSizeValue; import org.elasticsearch.common.util.BigArray; @@ -21,6 +22,7 @@ import org.elasticsearch.common.util.concurrent.EsExecutors; import org.elasticsearch.compute.data.BlockFactory; import org.elasticsearch.compute.data.Page; +import org.elasticsearch.core.Releasables; import org.elasticsearch.core.TimeValue; import org.elasticsearch.indices.CrankyCircuitBreakerService; import org.elasticsearch.threadpool.FixedExecutorBuilder; @@ -36,6 +38,7 @@ import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.in; /** * Base tests for {@link Operator}s that are not {@link SourceOperator} or {@link SinkOperator}. @@ -44,7 +47,7 @@ public abstract class OperatorTestCase extends AnyOperatorTestCase { /** * Valid input to be sent to {@link #simple}; */ - protected abstract SourceOperator simpleInput(int size); + protected abstract SourceOperator simpleInput(BlockFactory blockFactory, int size); /** * Assert that output from {@link #simple} is correct for the @@ -80,15 +83,27 @@ public final void testSimpleLargeInput() { * in a sane way. */ public final void testSimpleCircuitBreaking() { - BigArrays bigArrays = new MockBigArrays(PageCacheRecycler.NON_RECYCLING_INSTANCE, smallEnoughToCircuitBreak()); + /* + * We build two CircuitBreakers - one for the input blocks and one for the operation itself. + * The input blocks don't count against the memory usage for the limited operator that we + * build. + */ + DriverContext inputFactoryContext = driverContext(); + BigArrays bigArrays = new MockBigArrays(PageCacheRecycler.NON_RECYCLING_INSTANCE, smallEnoughToCircuitBreak()) + .withCircuitBreaking(); + List input = CannedSourceOperator.collectPages(simpleInput(inputFactoryContext.blockFactory(), between(1_000, 10_000))); CircuitBreaker breaker = bigArrays.breakerService().getBreaker(CircuitBreaker.REQUEST); BlockFactory blockFactory = BlockFactory.getInstance(breaker, bigArrays); Exception e = expectThrows( CircuitBreakingException.class, - () -> assertSimple(new DriverContext(bigArrays, blockFactory), between(1_000, 10_000)) + () -> drive(simple(bigArrays).get(new DriverContext(bigArrays, blockFactory)), input.iterator()) ); assertThat(e.getMessage(), equalTo(MockBigArrays.ERROR_MESSAGE)); assertThat(bigArrays.breakerService().getBreaker(CircuitBreaker.REQUEST).getUsed(), equalTo(0L)); + + // Note the lack of try/finally here - we're asserting that when the driver throws an exception we clear the breakers. + assertThat(bigArrays.breakerService().getBreaker(CircuitBreaker.REQUEST).getUsed(), equalTo(0L)); + assertThat(inputFactoryContext.breaker().getUsed(), equalTo(0L)); } /** @@ -98,15 +113,24 @@ public final void testSimpleCircuitBreaking() { * in ctors. */ public final void testSimpleWithCranky() { - CrankyCircuitBreakerService breaker = new CrankyCircuitBreakerService(); - BigArrays bigArrays = new MockBigArrays(PageCacheRecycler.NON_RECYCLING_INSTANCE, breaker).withCircuitBreaking(); - BlockFactory blockFactory = BlockFactory.getInstance(breaker.getBreaker("request"), bigArrays); + DriverContext inputFactoryContext = driverContext(); + List input = CannedSourceOperator.collectPages(simpleInput(inputFactoryContext.blockFactory(), between(1_000, 10_000))); + + CrankyCircuitBreakerService cranky = new CrankyCircuitBreakerService(); + BigArrays bigArrays = new MockBigArrays(PageCacheRecycler.NON_RECYCLING_INSTANCE, cranky).withCircuitBreaking(); + BlockFactory blockFactory = BlockFactory.getInstance(cranky.getBreaker(CircuitBreaker.REQUEST), bigArrays); try { - assertSimple(new DriverContext(bigArrays, blockFactory), between(1_000, 10_000)); + List result = drive(simple(bigArrays).get(new DriverContext(bigArrays, blockFactory)), input.iterator()); + Releasables.close(() -> Iterators.map(result.iterator(), p -> p::releaseBlocks)); // Either we get lucky and cranky doesn't throw and the test completes or we don't and it throws } catch (CircuitBreakingException e) { + logger.info("broken", e); assertThat(e.getMessage(), equalTo(CrankyCircuitBreakerService.ERROR_MESSAGE)); } + + // Note the lack of try/finally here - we're asserting that when the driver throws an exception we clear the breakers. + assertThat(bigArrays.breakerService().getBreaker(CircuitBreaker.REQUEST).getUsed(), equalTo(0L)); + assertThat(inputFactoryContext.breaker().getUsed(), equalTo(0L)); } /** @@ -139,10 +163,12 @@ protected final List oneDriverPerPageList(Iterator> source, Sup } private void assertSimple(DriverContext context, int size) { - List input = CannedSourceOperator.collectPages(simpleInput(size)); + List input = CannedSourceOperator.collectPages(simpleInput(context.blockFactory(), size)); + // Clone the input so that the operator can close it, then, later, we can read it again to build the assertion. + List inputClone = CannedSourceOperator.deepCopyOf(input); BigArrays bigArrays = context.bigArrays().withCircuitBreaking(); List results = drive(simple(bigArrays).get(context), input.iterator()); - assertSimpleOutput(input, results); + assertSimpleOutput(inputClone, results); results.forEach(Page::releaseBlocks); assertThat(bigArrays.breakerService().getBreaker(CircuitBreaker.REQUEST).getUsed(), equalTo(0L)); } @@ -180,7 +206,11 @@ public static void runDriver(List drivers) { "dummy-session", new DriverContext(BigArrays.NON_RECYCLING_INSTANCE, BlockFactory.getNonBreakingInstance()), () -> "dummy-driver", - new SequenceLongBlockSourceOperator(LongStream.range(0, between(1, 100)), between(1, 100)), + new SequenceLongBlockSourceOperator( + BlockFactory.getNonBreakingInstance(), + LongStream.range(0, between(1, 100)), + between(1, 100) + ), List.of(), new PageConsumerOperator(page -> {}), Driver.DEFAULT_STATUS_INTERVAL, diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/ProjectOperatorTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/ProjectOperatorTests.java index baa7842bdc1f9..6ec0183bbf224 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/ProjectOperatorTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/ProjectOperatorTests.java @@ -90,7 +90,7 @@ private List randomProjection(int size) { } @Override - protected SourceOperator simpleInput(int end) { + protected SourceOperator simpleInput(BlockFactory blockFactory, int end) { return new TupleBlockSourceOperator(blockFactory, LongStream.range(0, end).mapToObj(l -> Tuple.tuple(l, end - l))); } diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/SequenceLongBlockSourceOperator.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/SequenceLongBlockSourceOperator.java index 0aa78f3ad0ab3..f7c3ee825d695 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/SequenceLongBlockSourceOperator.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/SequenceLongBlockSourceOperator.java @@ -21,23 +21,27 @@ public class SequenceLongBlockSourceOperator extends AbstractBlockSourceOperator static final int DEFAULT_MAX_PAGE_POSITIONS = 8 * 1024; + private final BlockFactory blockFactory; + private final long[] values; - public SequenceLongBlockSourceOperator(LongStream values) { - this(values, DEFAULT_MAX_PAGE_POSITIONS); + public SequenceLongBlockSourceOperator(BlockFactory blockFactory, LongStream values) { + this(blockFactory, values, DEFAULT_MAX_PAGE_POSITIONS); } - public SequenceLongBlockSourceOperator(LongStream values, int maxPagePositions) { + public SequenceLongBlockSourceOperator(BlockFactory blockFactory, LongStream values, int maxPagePositions) { super(maxPagePositions); + this.blockFactory = blockFactory; this.values = values.toArray(); } - public SequenceLongBlockSourceOperator(List values) { - this(values, DEFAULT_MAX_PAGE_POSITIONS); + public SequenceLongBlockSourceOperator(BlockFactory blockFactory, List values) { + this(blockFactory, values, DEFAULT_MAX_PAGE_POSITIONS); } - public SequenceLongBlockSourceOperator(List values, int maxPagePositions) { + public SequenceLongBlockSourceOperator(BlockFactory blockFactory, List values, int maxPagePositions) { super(maxPagePositions); + this.blockFactory = blockFactory; this.values = values.stream().mapToLong(Long::longValue).toArray(); } @@ -48,7 +52,7 @@ protected Page createPage(int positionOffset, int length) { array[i] = values[positionOffset + i]; } currentPosition += length; - return new Page(BlockFactory.getNonBreakingInstance().newLongArrayVector(array, array.length).asBlock()); // TODO: just for compile + return new Page(blockFactory.newLongArrayVector(array, array.length).asBlock()); // TODO: just for compile } protected int remaining() { diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/StringExtractOperatorTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/StringExtractOperatorTests.java index f3c67f18589fa..1e72ecb0c3ee4 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/StringExtractOperatorTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/StringExtractOperatorTests.java @@ -11,6 +11,7 @@ import org.elasticsearch.common.unit.ByteSizeValue; import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.BlockFactory; import org.elasticsearch.compute.data.BytesRefBlock; import org.elasticsearch.compute.data.Page; @@ -25,7 +26,7 @@ public class StringExtractOperatorTests extends OperatorTestCase { @Override - protected SourceOperator simpleInput(int end) { + protected SourceOperator simpleInput(BlockFactory blockFactory, int end) { List input = LongStream.range(0, end) .mapToObj(l -> new BytesRef("word1_" + l + " word2_" + l + " word3_" + l)) .collect(Collectors.toList()); diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/TupleBlockSourceOperator.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/TupleBlockSourceOperator.java index 78cff5897c917..9b87dbe01224a 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/TupleBlockSourceOperator.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/TupleBlockSourceOperator.java @@ -26,10 +26,6 @@ public class TupleBlockSourceOperator extends AbstractBlockSourceOperator { private final List> values; - public TupleBlockSourceOperator(Stream> values) { - this(BlockFactory.getNonBreakingInstance(), values, DEFAULT_MAX_PAGE_POSITIONS); - } - public TupleBlockSourceOperator(BlockFactory blockFactory, Stream> values) { this(blockFactory, values, DEFAULT_MAX_PAGE_POSITIONS); } @@ -40,14 +36,14 @@ public TupleBlockSourceOperator(BlockFactory blockFactory, Stream> values) { - this(values, DEFAULT_MAX_PAGE_POSITIONS); + public TupleBlockSourceOperator(BlockFactory blockFactory, List> values) { + this(blockFactory, values, DEFAULT_MAX_PAGE_POSITIONS); } - public TupleBlockSourceOperator(List> values, int maxPagePositions) { + public TupleBlockSourceOperator(BlockFactory blockFactory, List> values, int maxPagePositions) { super(maxPagePositions); + this.blockFactory = blockFactory; this.values = values; - blockFactory = BlockFactory.getNonBreakingInstance(); } @Override diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/topn/ExtractorTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/topn/ExtractorTests.java index d79fde19f5487..9c4358e5d9ee0 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/topn/ExtractorTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/topn/ExtractorTests.java @@ -14,6 +14,7 @@ import org.elasticsearch.common.breaker.CircuitBreaker; import org.elasticsearch.common.breaker.NoopCircuitBreaker; import org.elasticsearch.compute.data.Block; +import org.elasticsearch.compute.data.BlockFactory; import org.elasticsearch.compute.data.BlockTestUtils; import org.elasticsearch.compute.data.BlockUtils; import org.elasticsearch.compute.data.DocVector; @@ -142,7 +143,13 @@ public void testNotInKey() { ValueExtractor.extractorFor(testCase.type, testCase.encoder.toUnsortable(), false, value).writeValue(valuesBuilder, 0); assertThat(valuesBuilder.length(), greaterThan(0)); - ResultBuilder result = ResultBuilder.resultBuilderFor(testCase.type, testCase.encoder.toUnsortable(), false, 1); + ResultBuilder result = ResultBuilder.resultBuilderFor( + BlockFactory.getNonBreakingInstance(), + testCase.type, + testCase.encoder.toUnsortable(), + false, + 1 + ); BytesRef values = valuesBuilder.bytesRefView(); result.decodeValue(values); assertThat(values.length, equalTo(0)); @@ -163,7 +170,13 @@ public void testInKey() { ValueExtractor.extractorFor(testCase.type, testCase.encoder.toUnsortable(), true, value).writeValue(valuesBuilder, 0); assertThat(valuesBuilder.length(), greaterThan(0)); - ResultBuilder result = ResultBuilder.resultBuilderFor(testCase.type, testCase.encoder.toUnsortable(), true, 1); + ResultBuilder result = ResultBuilder.resultBuilderFor( + BlockFactory.getNonBreakingInstance(), + testCase.type, + testCase.encoder.toUnsortable(), + true, + 1 + ); BytesRef keys = keysBuilder.bytesRefView(); if (testCase.type == ElementType.NULL) { assertThat(keys.length, equalTo(1)); diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/topn/TopNOperatorTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/topn/TopNOperatorTests.java index 7491ffde6766e..95f6613d3c0a4 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/topn/TopNOperatorTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/topn/TopNOperatorTests.java @@ -15,6 +15,7 @@ import org.elasticsearch.common.unit.ByteSizeValue; import org.elasticsearch.common.util.BigArrays; import org.elasticsearch.common.util.MockBigArrays; +import org.elasticsearch.common.util.PageCacheRecycler; import org.elasticsearch.compute.data.Block; import org.elasticsearch.compute.data.BlockFactory; import org.elasticsearch.compute.data.BooleanBlock; @@ -37,16 +38,21 @@ import org.elasticsearch.compute.operator.TupleBlockSourceOperator; import org.elasticsearch.core.Tuple; import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.test.ListMatcher; import org.elasticsearch.xpack.versionfield.Version; +import org.junit.After; +import java.lang.reflect.Field; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; @@ -143,8 +149,12 @@ protected String expectedToStringOfSimple() { } @Override - protected SourceOperator simpleInput(int size) { - return new SequenceLongBlockSourceOperator(LongStream.range(0, size).map(l -> ESTestCase.randomLong()), between(1, size * 2)); + protected SourceOperator simpleInput(BlockFactory blockFactory, int size) { + return new SequenceLongBlockSourceOperator( + blockFactory, + LongStream.range(0, size).map(l -> ESTestCase.randomLong()), + between(1, size * 2) + ); } @Override @@ -180,26 +190,48 @@ protected ByteSizeValue smallEnoughToCircuitBreak() { } public void testRamBytesUsed() { + RamUsageTester.Accumulator acc = new RamUsageTester.Accumulator() { + @Override + public long accumulateObject(Object o, long shallowSize, Map fieldValues, Collection queue) { + if (o instanceof ElementType) { + return 0; // shared + } + if (o instanceof TopNEncoder) { + return 0; // shared + } + if (o instanceof CircuitBreaker) { + return 0; // shared + } + if (o instanceof BlockFactory) { + return 0; // shard + } + return super.accumulateObject(o, shallowSize, fieldValues, queue); + } + }; int topCount = 10_000; // We under-count by a few bytes because of the lists. In that end that's fine, but we need to account for it here. - long underCount = 100; - TopNOperator op = new TopNOperator.TopNOperatorFactory( - topCount, - List.of(LONG), - List.of(DEFAULT_UNSORTABLE), - List.of(new TopNOperator.SortOrder(0, true, false)), - pageSize - ).get(driverContext()); - long actualEmpty = RamUsageTester.ramUsed(op) - RamUsageTester.ramUsed(LONG) - RamUsageTester.ramUsed(DEFAULT_UNSORTABLE) - - RamUsageTester.ramUsed(op.breaker()); - assertThat(op.ramBytesUsed(), both(greaterThan(actualEmpty - underCount)).and(lessThan(actualEmpty))); - // But when we fill it then we're quite close - for (Page p : CannedSourceOperator.collectPages(simpleInput(topCount))) { - op.addInput(p); + long underCount = 200; + DriverContext context = driverContext(); + try ( + TopNOperator op = new TopNOperator.TopNOperatorFactory( + topCount, + List.of(LONG), + List.of(DEFAULT_UNSORTABLE), + List.of(new TopNOperator.SortOrder(0, true, false)), + pageSize + ).get(context) + ) { + long actualEmpty = RamUsageTester.ramUsed(op, acc); + assertThat(op.ramBytesUsed(), both(greaterThan(actualEmpty - underCount)).and(lessThan(actualEmpty))); + // But when we fill it then we're quite close + for (Page p : CannedSourceOperator.collectPages(simpleInput(context.blockFactory(), topCount))) { + op.addInput(p); + } + long actualFull = RamUsageTester.ramUsed(op, acc); + assertThat(op.ramBytesUsed(), both(greaterThan(actualFull - underCount)).and(lessThan(actualFull))); + + // TODO empty it again and check. } - long actualFull = RamUsageTester.ramUsed(op) - RamUsageTester.ramUsed(LONG) - RamUsageTester.ramUsed(DEFAULT_UNSORTABLE) - - RamUsageTester.ramUsed(op.breaker()); - assertThat(op.ramBytesUsed(), both(greaterThan(actualFull - underCount)).and(lessThan(actualFull))); } public void testRandomTopN() { @@ -471,6 +503,7 @@ public void testCollectAllValues() { new CannedSourceOperator(List.of(new Page(blocks.toArray(Block[]::new))).iterator()), List.of( new TopNOperator( + blockFactory, nonBreakingBigArrays().breakerService().getBreaker("request"), topCount, elementTypes, @@ -559,6 +592,7 @@ public void testCollectAllValues_RandomMultiValues() { new CannedSourceOperator(List.of(new Page(blocks.toArray(Block[]::new))).iterator()), List.of( new TopNOperator( + blockFactory, nonBreakingBigArrays().breakerService().getBreaker("request"), topCount, elementTypes, @@ -590,9 +624,10 @@ private List> topNTwoColumns( try ( Driver driver = new Driver( driverContext, - new TupleBlockSourceOperator(inputValues, randomIntBetween(1, 1000)), + new TupleBlockSourceOperator(driverContext.blockFactory(), inputValues, randomIntBetween(1, 1000)), List.of( new TopNOperator( + driverContext.blockFactory(), nonBreakingBigArrays().breakerService().getBreaker("request"), limit, elementTypes, @@ -607,6 +642,7 @@ private List> topNTwoColumns( for (int i = 0; i < block1.getPositionCount(); i++) { outputValues.add(tuple(block1.isNull(i) ? null : block1.getLong(i), block2.isNull(i) ? null : block2.getLong(i))); } + page.releaseBlocks(); }), () -> {} ) @@ -848,6 +884,7 @@ private void assertSortingOnMV( TopNEncoder encoder, TopNOperator.SortOrder... sortOrders ) { + DriverContext driverContext = driverContext(); Block block = TestBlockBuilder.blockFromValues(values, blockType); assert block.mvOrdering() == Block.MvOrdering.UNORDERED : "Blocks created for this test must have unordered multi-values"; Page page = new Page(block); @@ -856,10 +893,11 @@ private void assertSortingOnMV( int topCount = randomIntBetween(1, values.size()); try ( Driver driver = new Driver( - driverContext(), + driverContext, new CannedSourceOperator(List.of(page).iterator()), List.of( new TopNOperator( + driverContext.blockFactory(), nonBreakingBigArrays().breakerService().getBreaker("request"), topCount, List.of(blockType), @@ -878,6 +916,7 @@ private void assertSortingOnMV( } public void testRandomMultiValuesTopN() { + DriverContext driverContext = driverContext(); int rows = randomIntBetween(50, 100); int topCount = randomIntBetween(1, rows); int blocksCount = randomIntBetween(20, 30); @@ -969,8 +1008,9 @@ public void testRandomMultiValuesTopN() { } List>> actualValues = new ArrayList<>(); - List results = this.drive( + List results = drive( new TopNOperator( + driverContext.blockFactory(), nonBreakingBigArrays().breakerService().getBreaker("request"), topCount, elementTypes, @@ -982,6 +1022,7 @@ public void testRandomMultiValuesTopN() { ); for (Page p : results) { readAsRows(actualValues, p); + p.releaseBlocks(); } List>> topNExpectedValues = expectedValues.stream() @@ -1003,13 +1044,15 @@ public void testIPSortingSingleValue() throws UnknownHostException { append(builder, new BytesRef(InetAddressPoint.encode(InetAddress.getByName(ip)))); } + DriverContext driverContext = driverContext(); List> actual = new ArrayList<>(); try ( Driver driver = new Driver( - driverContext(), + driverContext, new CannedSourceOperator(List.of(new Page(builder.build())).iterator()), List.of( new TopNOperator( + driverContext.blockFactory(), nonBreakingBigArrays().breakerService().getBreaker("request"), ips.size(), List.of(BYTES_REF), @@ -1128,12 +1171,14 @@ private void assertIPSortingOnMultiValues( } List> actual = new ArrayList<>(); + DriverContext driverContext = driverContext(); try ( Driver driver = new Driver( - driverContext(), + driverContext, new CannedSourceOperator(List.of(new Page(builder.build())).iterator()), List.of( new TopNOperator( + driverContext.blockFactory(), nonBreakingBigArrays().breakerService().getBreaker("request"), ips.size(), List.of(BYTES_REF), @@ -1210,12 +1255,14 @@ public void testZeroByte() { blocks.add(builderInt.build()); List> actual = new ArrayList<>(); + DriverContext driverContext = driverContext(); try ( Driver driver = new Driver( - driverContext(), + driverContext, new CannedSourceOperator(List.of(new Page(blocks.toArray(Block[]::new))).iterator()), List.of( new TopNOperator( + driverContext.blockFactory(), nonBreakingBigArrays().breakerService().getBreaker("request"), 2, List.of(BYTES_REF, INT), @@ -1243,10 +1290,55 @@ public void testZeroByte() { assertThat((Integer) actual.get(1).get(1), equalTo(100)); } + public void testErrorBeforeFullyDraining() { + int maxPageSize = between(1, 100); + int topCount = maxPageSize * 4; + int docCount = topCount * 10; + List> actual = new ArrayList<>(); + DriverContext driverContext = driverContext(); + try ( + Driver driver = new Driver( + driverContext, + new SequenceLongBlockSourceOperator(driverContext.blockFactory(), LongStream.range(0, docCount)), + List.of( + new TopNOperator( + driverContext.blockFactory(), + nonBreakingBigArrays().breakerService().getBreaker("request"), + topCount, + List.of(LONG), + List.of(DEFAULT_UNSORTABLE), + List.of(new TopNOperator.SortOrder(0, true, randomBoolean())), + maxPageSize + ) + ), + new PageConsumerOperator(p -> { + assertThat(p.getPositionCount(), equalTo(maxPageSize)); + if (actual.isEmpty()) { + readInto(actual, p); + } else { + p.releaseBlocks(); + throw new RuntimeException("boo"); + } + }), + () -> {} + ) + ) { + Exception e = expectThrows(RuntimeException.class, () -> runDriver(driver)); + assertThat(e.getMessage(), equalTo("boo")); + } + + ListMatcher values = matchesList(); + for (int i = 0; i < maxPageSize; i++) { + values = values.item((long) i); + } + assertMap(actual, matchesList().item(values)); + } + public void testCloseWithoutCompleting() { CircuitBreaker breaker = new MockBigArrays.LimitedBreaker(CircuitBreaker.REQUEST, ByteSizeValue.ofGb(1)); try ( TopNOperator op = new TopNOperator( + driverContext().blockFactory(), breaker, 2, List.of(INT), @@ -1257,7 +1349,23 @@ public void testCloseWithoutCompleting() { ) { op.addInput(new Page(new IntArrayVector(new int[] { 1 }, 1).asBlock())); } - assertThat(breaker.getUsed(), equalTo(0L)); + } + + private final List breakers = new ArrayList<>(); + + @Override + protected DriverContext driverContext() { // TODO remove this when the parent uses a breaking block factory + BigArrays bigArrays = new MockBigArrays(PageCacheRecycler.NON_RECYCLING_INSTANCE, ByteSizeValue.ofGb(1)).withCircuitBreaking(); + CircuitBreaker breaker = bigArrays.breakerService().getBreaker(CircuitBreaker.REQUEST); + breakers.add(breaker); + return new DriverContext(bigArrays, new BlockFactory(breaker, bigArrays)); + } + + @After + public void allBreakersEmpty() { + for (CircuitBreaker breaker : breakers) { + assertThat(breaker.getUsed(), equalTo(0L)); + } } @SuppressWarnings({ "unchecked", "rawtypes" }) diff --git a/x-pack/plugin/esql/qa/server/single-node/src/yamlRestTest/resources/rest-api-spec/test/50_index_patterns.yml b/x-pack/plugin/esql/qa/server/single-node/src/yamlRestTest/resources/rest-api-spec/test/50_index_patterns.yml index 280a32aa10cd3..bae0e623d12a3 100644 --- a/x-pack/plugin/esql/qa/server/single-node/src/yamlRestTest/resources/rest-api-spec/test/50_index_patterns.yml +++ b/x-pack/plugin/esql/qa/server/single-node/src/yamlRestTest/resources/rest-api-spec/test/50_index_patterns.yml @@ -235,35 +235,36 @@ disjoint_mappings: - length: { values: 1 } - match: { values.0.0: 2 } - - do: - esql.query: - body: - query: 'from test1,test2 | sort message1, message2 | eval x = message1, y = message2 + 1 | keep message1, message2, x, y' - - match: { columns.0.name: message1 } - - match: { columns.0.type: keyword } - - match: { columns.1.name: message2 } - - match: { columns.1.type: long } - - match: { columns.2.name: x } - - match: { columns.2.type: keyword } - - match: { columns.3.name: y } - - match: { columns.3.type: long } - - length: { values: 4 } - - match: { values.0.0: foo1 } - - match: { values.0.1: null } - - match: { values.0.2: foo1 } - - match: { values.0.3: null } - - match: { values.1.0: foo2 } - - match: { values.1.1: null } - - match: { values.1.2: foo2 } - - match: { values.1.3: null } - - match: { values.2.0: null } - - match: { values.2.1: 1 } - - match: { values.2.2: null } - - match: { values.2.3: 2 } - - match: { values.3.0: null } - - match: { values.3.1: 2 } - - match: { values.3.2: null } - - match: { values.3.3: 3 } +# AwaitsFix https://github.com/elastic/elasticsearch/issues/99826 +# - do: +# esql.query: +# body: +# query: 'from test1,test2 | sort message1, message2 | eval x = message1, y = message2 + 1 | keep message1, message2, x, y' +# - match: { columns.0.name: message1 } +# - match: { columns.0.type: keyword } +# - match: { columns.1.name: message2 } +# - match: { columns.1.type: long } +# - match: { columns.2.name: x } +# - match: { columns.2.type: keyword } +# - match: { columns.3.name: y } +# - match: { columns.3.type: long } +# - length: { values: 4 } +# - match: { values.0.0: foo1 } +# - match: { values.0.1: null } +# - match: { values.0.2: foo1 } +# - match: { values.0.3: null } +# - match: { values.1.0: foo2 } +# - match: { values.1.1: null } +# - match: { values.1.2: foo2 } +# - match: { values.1.3: null } +# - match: { values.2.0: null } +# - match: { values.2.1: 1 } +# - match: { values.2.2: null } +# - match: { values.2.3: 2 } +# - match: { values.3.0: null } +# - match: { values.3.1: 2 } +# - match: { values.3.2: null } +# - match: { values.3.3: 3 } --- same_name_different_type: diff --git a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/metadata-ignoreCsvTests.csv-spec b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/metadata-ignoreCsvTests.csv-spec index d89f3337c081b..82fd27416c526 100644 --- a/x-pack/plugin/esql/qa/testFixtures/src/main/resources/metadata-ignoreCsvTests.csv-spec +++ b/x-pack/plugin/esql/qa/testFixtures/src/main/resources/metadata-ignoreCsvTests.csv-spec @@ -7,7 +7,8 @@ emp_no:integer |_index:keyword |_version:long 10002 |employees |1 ; -aliasWithSameName +# AwaitsFix https://github.com/elastic/elasticsearch/issues/99826 +aliasWithSameName-Ignore from employees [metadata _index, _version] | sort emp_no | limit 2 | eval _index = _index, _version = _version | keep emp_no, _index, _version; emp_no:integer |_index:keyword |_version:long diff --git a/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/EsqlActionIT.java b/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/EsqlActionIT.java index f8aeee1569f2e..800e36949f5ea 100644 --- a/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/EsqlActionIT.java +++ b/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/EsqlActionIT.java @@ -9,7 +9,6 @@ import org.elasticsearch.Build; import org.elasticsearch.action.admin.indices.alias.IndicesAliasesRequest; -import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest; import org.elasticsearch.action.bulk.BulkRequestBuilder; import org.elasticsearch.action.delete.DeleteRequest; import org.elasticsearch.action.index.IndexRequest; @@ -109,32 +108,33 @@ public void testFromStatsGroupingAvgWithAliases() { } private void testFromStatsGroupingAvgImpl(String command, String expectedGroupName, String expectedFieldName) { - EsqlQueryResponse results = run(command); - logger.info(results); - Assert.assertEquals(2, results.columns().size()); - - // assert column metadata - ColumnInfo valuesColumn = results.columns().get(0); - assertEquals(expectedFieldName, valuesColumn.name()); - assertEquals("double", valuesColumn.type()); - ColumnInfo groupColumn = results.columns().get(1); - assertEquals(expectedGroupName, groupColumn.name()); - assertEquals("long", groupColumn.type()); + try (EsqlQueryResponse results = run(command)) { + logger.info(results); + Assert.assertEquals(2, results.columns().size()); - // assert column values - List> valueValues = getValuesList(results); - assertEquals(2, valueValues.size()); - // This is loathsome, find a declarative way to assert the expected output. - if ((long) valueValues.get(0).get(1) == 1L) { - assertEquals(42.0, (double) valueValues.get(0).get(0), 0.0); - assertEquals(2L, (long) valueValues.get(1).get(1)); - assertEquals(44.0, (double) valueValues.get(1).get(0), 0.0); - } else if ((long) valueValues.get(0).get(1) == 2L) { - assertEquals(42.0, (double) valueValues.get(1).get(0), 0.0); - assertEquals(1L, (long) valueValues.get(1).get(1)); - assertEquals(44.0, (double) valueValues.get(0).get(0), 0.0); - } else { - fail("Unexpected group value: " + valueValues.get(0).get(0)); + // assert column metadata + ColumnInfo valuesColumn = results.columns().get(0); + assertEquals(expectedFieldName, valuesColumn.name()); + assertEquals("double", valuesColumn.type()); + ColumnInfo groupColumn = results.columns().get(1); + assertEquals(expectedGroupName, groupColumn.name()); + assertEquals("long", groupColumn.type()); + + // assert column values + List> valueValues = getValuesList(results); + assertEquals(2, valueValues.size()); + // This is loathsome, find a declarative way to assert the expected output. + if ((long) valueValues.get(0).get(1) == 1L) { + assertEquals(42.0, (double) valueValues.get(0).get(0), 0.0); + assertEquals(2L, (long) valueValues.get(1).get(1)); + assertEquals(44.0, (double) valueValues.get(1).get(0), 0.0); + } else if ((long) valueValues.get(0).get(1) == 2L) { + assertEquals(42.0, (double) valueValues.get(1).get(0), 0.0); + assertEquals(1L, (long) valueValues.get(1).get(1)); + assertEquals(44.0, (double) valueValues.get(0).get(0), 0.0); + } else { + fail("Unexpected group value: " + valueValues.get(0).get(0)); + } } } @@ -211,19 +211,20 @@ public void testFromGroupingByNumericFieldWithNulls() { } } client().admin().indices().prepareRefresh("test").get(); - EsqlQueryResponse results = run("from test | stats avg(count) by data | sort data"); - logger.info(results); + try (EsqlQueryResponse results = run("from test | stats avg(count) by data | sort data")) { + logger.info(results); - assertThat(results.columns(), hasSize(2)); - assertEquals("avg(count)", results.columns().get(0).name()); - assertEquals("double", results.columns().get(0).type()); - assertEquals("data", results.columns().get(1).name()); - assertEquals("long", results.columns().get(1).type()); + assertThat(results.columns(), hasSize(2)); + assertEquals("avg(count)", results.columns().get(0).name()); + assertEquals("double", results.columns().get(0).type()); + assertEquals("data", results.columns().get(1).name()); + assertEquals("long", results.columns().get(1).type()); - record Group(Long data, Double avg) {} - List expectedGroups = List.of(new Group(1L, 42.0), new Group(2L, 44.0), new Group(99L, null), new Group(null, 12.0)); - List actualGroups = getValuesList(results).stream().map(l -> new Group((Long) l.get(1), (Double) l.get(0))).toList(); - assertThat(actualGroups, equalTo(expectedGroups)); + record Group(Long data, Double avg) {} + List expectedGroups = List.of(new Group(1L, 42.0), new Group(2L, 44.0), new Group(99L, null), new Group(null, 12.0)); + List actualGroups = getValuesList(results).stream().map(l -> new Group((Long) l.get(1), (Double) l.get(0))).toList(); + assertThat(actualGroups, equalTo(expectedGroups)); + } } public void testFromStatsGroupingByKeyword() { @@ -332,18 +333,19 @@ record Group(double avg, long mi, long ma, long s, long c, String color) {} } public void testFromSortWithTieBreakerLimit() { - EsqlQueryResponse results = run("from test | sort data, count desc, time | limit 5 | keep data, count, time"); - logger.info(results); - assertThat( - getValuesList(results), - contains( - List.of(1L, 44L, epoch + 2), - List.of(1L, 44L, epoch + 6), - List.of(1L, 44L, epoch + 10), - List.of(1L, 44L, epoch + 14), - List.of(1L, 44L, epoch + 18) - ) - ); + try (EsqlQueryResponse results = run("from test | sort data, count desc, time | limit 5 | keep data, count, time")) { + logger.info(results); + assertThat( + getValuesList(results), + contains( + List.of(1L, 44L, epoch + 2), + List.of(1L, 44L, epoch + 6), + List.of(1L, 44L, epoch + 10), + List.of(1L, 44L, epoch + 14), + List.of(1L, 44L, epoch + 18) + ) + ); + } } public void testFromStatsProjectGroup() { @@ -778,10 +780,11 @@ public void testFromStatsLimit() { } public void testFromLimit() { - EsqlQueryResponse results = run("from test | keep data | limit 2"); - logger.info(results); - assertThat(results.columns(), contains(new ColumnInfo("data", "long"))); - assertThat(getValuesList(results), contains(anyOf(contains(1L), contains(2L)), anyOf(contains(1L), contains(2L)))); + try (EsqlQueryResponse results = run("from test | keep data | limit 2")) { + logger.info(results); + assertThat(results.columns(), contains(new ColumnInfo("data", "long"))); + assertThat(getValuesList(results), contains(anyOf(contains(1L), contains(2L)), anyOf(contains(1L), contains(2L)))); + } } public void testDropAllColumns() { @@ -1000,27 +1003,25 @@ public void testTopNPushedToLuceneOnSortedIndex() { ); int limit = randomIntBetween(1, 5); - EsqlQueryResponse results = run("from sorted_test_index | sort time " + sortOrder + " | limit " + limit + " | keep time"); - logger.info(results); - Assert.assertEquals(1, results.columns().size()); - Assert.assertEquals(limit, getValuesList(results).size()); - - // assert column metadata - assertEquals("time", results.columns().get(0).name()); - assertEquals("long", results.columns().get(0).type()); + try (EsqlQueryResponse results = run("from sorted_test_index | sort time " + sortOrder + " | limit " + limit + " | keep time")) { + logger.info(results); + Assert.assertEquals(1, results.columns().size()); + Assert.assertEquals(limit, getValuesList(results).size()); - boolean sortedDesc = "desc".equals(sortOrder); - var expected = LongStream.range(0, 40) - .map(i -> epoch + i) - .boxed() - .sorted(sortedDesc ? reverseOrder() : naturalOrder()) - .limit(limit) - .toList(); - var actual = getValuesList(results).stream().map(l -> (Long) l.get(0)).toList(); - assertThat(actual, equalTo(expected)); + // assert column metadata + assertEquals("time", results.columns().get(0).name()); + assertEquals("long", results.columns().get(0).type()); - // clean-up - client().admin().indices().delete(new DeleteIndexRequest("sorted_test_index")).actionGet(); + boolean sortedDesc = "desc".equals(sortOrder); + var expected = LongStream.range(0, 40) + .map(i -> epoch + i) + .boxed() + .sorted(sortedDesc ? reverseOrder() : naturalOrder()) + .limit(limit) + .toList(); + var actual = getValuesList(results).stream().map(l -> (Long) l.get(0)).toList(); + assertThat(actual, equalTo(expected)); + } } /* diff --git a/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/EsqlDisruptionIT.java b/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/EsqlDisruptionIT.java index 19893bf8072f1..2636a7cf3c133 100644 --- a/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/EsqlDisruptionIT.java +++ b/x-pack/plugin/esql/src/internalClusterTest/java/org/elasticsearch/xpack/esql/action/EsqlDisruptionIT.java @@ -7,6 +7,7 @@ package org.elasticsearch.xpack.esql.action; +import org.apache.lucene.tests.util.LuceneTestCase; import org.elasticsearch.action.ActionFuture; import org.elasticsearch.cluster.coordination.Coordinator; import org.elasticsearch.cluster.coordination.FollowersChecker; @@ -18,6 +19,7 @@ import org.elasticsearch.test.ESIntegTestCase; import org.elasticsearch.test.disruption.NetworkDisruption; import org.elasticsearch.test.disruption.ServiceDisruptionScheme; +import org.elasticsearch.test.junit.annotations.TestLogging; import org.elasticsearch.test.transport.MockTransportService; import org.elasticsearch.transport.TransportSettings; @@ -29,7 +31,9 @@ import static org.elasticsearch.test.ESIntegTestCase.Scope.TEST; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked; +@TestLogging(value = "org.elasticsearch.indices.breaker:TRACE", reason = "failing") @ESIntegTestCase.ClusterScope(scope = TEST, minNumDataNodes = 2, maxNumDataNodes = 4) +@LuceneTestCase.AwaitsFix(bugUrl = "https://github.com/elastic/elasticsearch/issues/99173") public class EsqlDisruptionIT extends EsqlActionIT { // copied from AbstractDisruptionTestCase From 0fc9fa232c3107308b7a4182136fcfee12a4b007 Mon Sep 17 00:00:00 2001 From: David Roberts Date: Thu, 28 Sep 2023 18:43:42 +0100 Subject: [PATCH 16/32] [ML] Reducing use of DiscoveryNode.getVersion (#100024) Remove as much usage as possible of DiscoveryNode.getVersion. Generally for these cases we should be using MlConfigVersion. --- .../ml/action/TrainedModelValidator.java | 2 +- ...ransportStartDataFrameAnalyticsAction.java | 2 +- .../xpack/ml/job/JobNodeSelector.java | 3 +- .../task/OpenJobPersistentTasksExecutor.java | 2 +- .../ml/action/TrainedModelValidatorTests.java | 45 ++++++++++--------- .../xpack/ml/job/JobNodeSelectorTests.java | 35 ++++++++++----- 6 files changed, 54 insertions(+), 35 deletions(-) diff --git a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TrainedModelValidator.java b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TrainedModelValidator.java index d1d66299db67f..acd0a124d59c2 100644 --- a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TrainedModelValidator.java +++ b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TrainedModelValidator.java @@ -59,7 +59,7 @@ static void validateMinimumVersion(ModelPackageConfig resolvedModelPackageConfig if (MlConfigVersion.getMinMlConfigVersion(state.nodes()).before(minimumVersion)) { throw new ActionRequestValidationException().addValidationError( format( - "The model [%s] requires that all nodes are at least version [%s]", + "The model [%s] requires that all nodes have ML config version [%s] or higher", resolvedModelPackageConfig.getPackagedModelId(), resolvedModelPackageConfig.getMinimumVersion() ) diff --git a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportStartDataFrameAnalyticsAction.java b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportStartDataFrameAnalyticsAction.java index 2a1844ea1fccf..ab215106c8ed0 100644 --- a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportStartDataFrameAnalyticsAction.java +++ b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportStartDataFrameAnalyticsAction.java @@ -807,7 +807,7 @@ public static String nodeFilter(DiscoveryNode node, TaskParams params) { + id + "] on node [" + JobNodeSelector.nodeNameAndVersion(node) - + "], because the data frame analytics requires a node of version [" + + "], because the data frame analytics requires a node with ML config version [" + TaskParams.VERSION_INTRODUCED + "] or higher"; } diff --git a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/job/JobNodeSelector.java b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/job/JobNodeSelector.java index 2997af2e5a1a8..a24e671d1fe25 100644 --- a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/job/JobNodeSelector.java +++ b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/job/JobNodeSelector.java @@ -14,6 +14,7 @@ import org.elasticsearch.common.unit.ByteSizeValue; import org.elasticsearch.core.Tuple; import org.elasticsearch.persistent.PersistentTasksCustomMetadata; +import org.elasticsearch.xpack.core.ml.MlConfigVersion; import org.elasticsearch.xpack.ml.MachineLearning; import org.elasticsearch.xpack.ml.autoscaling.NativeMemoryCapacity; import org.elasticsearch.xpack.ml.process.MlMemoryTracker; @@ -346,7 +347,7 @@ static String nodeNameOrId(DiscoveryNode node) { public static String nodeNameAndVersion(DiscoveryNode node) { String nodeNameOrID = nodeNameOrId(node); StringBuilder builder = new StringBuilder("{").append(nodeNameOrID).append('}'); - builder.append('{').append("version=").append(node.getVersion()).append('}'); + builder.append('{').append("ML config version=").append(MlConfigVersion.fromNode(node)).append('}'); return builder.toString(); } diff --git a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/job/task/OpenJobPersistentTasksExecutor.java b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/job/task/OpenJobPersistentTasksExecutor.java index b19c0fb670a59..15b1993dc0586 100644 --- a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/job/task/OpenJobPersistentTasksExecutor.java +++ b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/job/task/OpenJobPersistentTasksExecutor.java @@ -179,7 +179,7 @@ public static String nodeFilter(DiscoveryNode node, Job job) { + jobId + "] on node [" + JobNodeSelector.nodeNameAndVersion(node) - + "], because the job's model snapshot requires a node of version [" + + "], because the job's model snapshot requires a node with ML config version [" + job.getModelSnapshotMinVersion() + "] or higher"; } diff --git a/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/action/TrainedModelValidatorTests.java b/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/action/TrainedModelValidatorTests.java index d99147e6b4e98..f8755b282c6a1 100644 --- a/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/action/TrainedModelValidatorTests.java +++ b/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/action/TrainedModelValidatorTests.java @@ -11,13 +11,16 @@ import org.elasticsearch.action.ActionRequestValidationException; import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.node.DiscoveryNode; +import org.elasticsearch.cluster.node.DiscoveryNodeRole; +import org.elasticsearch.cluster.node.DiscoveryNodeUtils; import org.elasticsearch.cluster.node.DiscoveryNodes; -import org.elasticsearch.index.IndexVersion; +import org.elasticsearch.common.transport.TransportAddress; import org.elasticsearch.test.ESTestCase; import org.elasticsearch.xpack.core.ml.MlConfigVersion; import org.elasticsearch.xpack.core.ml.inference.trainedmodel.ModelPackageConfig; import org.elasticsearch.xpack.core.ml.inference.trainedmodel.ModelPackageConfigTests; +import java.net.InetAddress; import java.util.Map; import static org.mockito.Mockito.mock; @@ -31,12 +34,14 @@ public void testValidateMinimumVersion() { .setMinimumVersion("9999.0.0") .build(); - DiscoveryNode node = mock(DiscoveryNode.class); final Map attributes = Map.of(MlConfigVersion.ML_CONFIG_VERSION_NODE_ATTR, MlConfigVersion.CURRENT.toString()); - when(node.getAttributes()).thenReturn(attributes); - when(node.getVersion()).thenReturn(Version.CURRENT); - when(node.getMinIndexVersion()).thenReturn(IndexVersion.current()); - when(node.getId()).thenReturn("node1"); + DiscoveryNode node = DiscoveryNodeUtils.create( + "node1name", + "node1", + new TransportAddress(InetAddress.getLoopbackAddress(), 9301), + attributes, + DiscoveryNodeRole.roles() + ); DiscoveryNodes nodes = DiscoveryNodes.builder().add(node).build(); @@ -52,7 +57,7 @@ public void testValidateMinimumVersion() { assertEquals( "Validation Failed: 1: The model [" + packageConfig.getPackagedModelId() - + "] requires that all nodes are at least version [9999.0.0];", + + "] requires that all nodes have ML config version [9999.0.0] or higher;", e.getMessage() ); } @@ -63,12 +68,11 @@ public void testValidateMinimumVersion() { ModelPackageConfigTests.randomModulePackageConfig() ).setMinimumVersion(MlConfigVersion.CURRENT.toString()).build(); - DiscoveryNode node = mock(DiscoveryNode.class); - final Map attributes = Map.of(MlConfigVersion.ML_CONFIG_VERSION_NODE_ATTR, MlConfigVersion.V_8_7_0.toString()); - when(node.getAttributes()).thenReturn(attributes); - when(node.getVersion()).thenReturn(Version.V_8_7_0); - when(node.getMinIndexVersion()).thenReturn(IndexVersion.current()); - when(node.getId()).thenReturn("node1"); + DiscoveryNode node = DiscoveryNodeUtils.create( + "node1", + new TransportAddress(InetAddress.getLoopbackAddress(), 9300), + Version.V_8_7_0 + ); DiscoveryNodes nodes = DiscoveryNodes.builder().add(node).build(); @@ -82,9 +86,9 @@ public void testValidateMinimumVersion() { assertEquals( "Validation Failed: 1: The model [" + packageConfigCurrent.getPackagedModelId() - + "] requires that all nodes are at least version [" + + "] requires that all nodes have ML config version [" + MlConfigVersion.CURRENT - + "];", + + "] or higher;", e.getMessage() ); } @@ -95,12 +99,11 @@ public void testValidateMinimumVersion() { ModelPackageConfigTests.randomModulePackageConfig() ).setMinimumVersion("_broken_version_").build(); - DiscoveryNode node = mock(DiscoveryNode.class); - final Map attributes = Map.of(MlConfigVersion.ML_CONFIG_VERSION_NODE_ATTR, MlConfigVersion.V_8_7_0.toString()); - when(node.getAttributes()).thenReturn(attributes); - when(node.getVersion()).thenReturn(Version.V_8_7_0); - when(node.getMinIndexVersion()).thenReturn(IndexVersion.current()); - when(node.getId()).thenReturn("node1"); + DiscoveryNode node = DiscoveryNodeUtils.create( + "node1", + new TransportAddress(InetAddress.getLoopbackAddress(), 9300), + Version.V_8_7_0 + ); DiscoveryNodes nodes = DiscoveryNodes.builder().add(node).build(); diff --git a/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/job/JobNodeSelectorTests.java b/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/job/JobNodeSelectorTests.java index 19546b37c00cd..ab815aad543b8 100644 --- a/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/job/JobNodeSelectorTests.java +++ b/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/job/JobNodeSelectorTests.java @@ -42,7 +42,6 @@ import java.util.Collection; import java.util.Collections; import java.util.Date; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -81,12 +80,25 @@ public void setup() { when(memoryTracker.getJobMemoryRequirement(anyString(), anyString())).thenReturn(JOB_MEMORY_REQUIREMENT.getBytes()); } - public void testNodeNameAndVersion() { + public void testNodeNameAndVersionForRecentNode() { TransportAddress ta = new TransportAddress(InetAddress.getLoopbackAddress(), 9300); - Map attributes = new HashMap<>(); - attributes.put("unrelated", "attribute"); + Map attributes = Map.of(MlConfigVersion.ML_CONFIG_VERSION_NODE_ATTR, "10.0.0", "unrelated", "attribute"); DiscoveryNode node = DiscoveryNodeUtils.create("_node_name1", "_node_id1", ta, attributes, ROLES_WITHOUT_ML); - assertEquals("{_node_name1}{version=" + node.getVersion() + "}", JobNodeSelector.nodeNameAndVersion(node)); + assertEquals("{_node_name1}{ML config version=10.0.0}", JobNodeSelector.nodeNameAndVersion(node)); + } + + public void testNodeNameAndVersionForOldNode() { + TransportAddress ta = new TransportAddress(InetAddress.getLoopbackAddress(), 9300); + Map attributes = Map.of("unrelated", "attribute"); + DiscoveryNode node = new DiscoveryNode( + "_node_name2", + "_node_id2", + ta, + attributes, + ROLES_WITH_ML, + VersionInformation.inferVersions(Version.V_8_7_0) + ); + assertEquals("{_node_name2}{ML config version=8.7.0}", JobNodeSelector.nodeNameAndVersion(node)); } public void testNodeNameAndMlAttributes() { @@ -869,12 +881,12 @@ public void testSelectLeastLoadedMlNode_reasonsAreInDeterministicOrder() { assertThat( result.getExplanation(), equalTo( - "Not opening job [incompatible_type_job] on node [{_node_name1}{version=" - + Version.CURRENT + "Not opening job [incompatible_type_job] on node [{_node_name1}{ML config version=" + + MlConfigVersion.CURRENT + "}], " + "because this node does not support jobs of type [incompatible_type]|" - + "Not opening job [incompatible_type_job] on node [{_node_name2}{version=" - + Version.CURRENT + + "Not opening job [incompatible_type_job] on node [{_node_name2}{ML config version=" + + MlConfigVersion.CURRENT + "}], " + "because this node does not support jobs of type [incompatible_type]" ) @@ -946,7 +958,10 @@ public void testSelectLeastLoadedMlNode_noNodesMatchingModelSnapshotMinVersion() node -> nodeFilter(node, job) ); PersistentTasksCustomMetadata.Assignment result = jobNodeSelector.selectNode(10, 2, 30, MAX_JOB_BYTES, false); - assertThat(result.getExplanation(), containsString("job's model snapshot requires a node of version [7.3.0] or higher")); + assertThat( + result.getExplanation(), + containsString("job's model snapshot requires a node with ML config version [7.3.0] or higher") + ); assertNull(result.getExecutorNode()); } From f202ad02fe65da38bcbc91d626bb087ca2ce236e Mon Sep 17 00:00:00 2001 From: Andrei Dan Date: Thu, 28 Sep 2023 18:48:17 +0100 Subject: [PATCH 17/32] GET _data_stream displays both ILM and DSL information (#99947) This add support to the `GET _data_stream` API for displaying the value of the `index.lifecycle.prefer_ilm` setting both at the backing index level and at the top level (top level meaning, similarly to the existing `ilm_policy` field, the value in the index template that's backing the data stream), an `ilm_policy` field for each backing index displaying the actual ILM policy configured for the index itself, a `managed_by` field for each backing index indicating who manages this index (the possible values are: `Index Lifecycle Management`, `Data stream lifecycle`, and `Unmanaged`). This also adds a top level field to indicate which system would manage the next generation index for this data stream based on the current configuration. This field is called `next_generation_managed_by` and the same values as the indices level `managed_by` field has are available. An example output for a data stream that has 2 backing indices managed by ILM and the write index by DSL: ``` { "data_streams": [{ "name": "datastream-psnyudmbitp", "timestamp_field": { "name": "@timestamp" }, "indices": [{ "index_name": ".ds-datastream-psnyudmbitp-2023.09.27-000001", "index_uuid": "kyw0WEXvS8-ahchYS10NRQ", "prefer_ilm": true, "ilm_policy": "policy-uVBEI", "managed_by": "Index Lifecycle Management" }, { "index_name": ".ds-datastream-psnyudmbitp-2023.09.27-000002", "index_uuid": "pDLdc4DERwO54GRzDr4krw", "prefer_ilm": true, "ilm_policy": "policy-uVBEI", "managed_by": "Index Lifecycle Management" }, { "index_name": ".ds-datastream-psnyudmbitp-2023.09.27-000003", "index_uuid": "gYZirLKcS3mlc1c3oHRpYw", "prefer_ilm": false, "ilm_policy": "policy-uVBEI", "managed_by": "Data stream lifecycle" }], "generation": 3, "status": "YELLOW", "template": "indextemplate-obcvkbjqand", "lifecycle": { "enabled": true, "data_retention": "90d" }, "ilm_policy": "policy-uVBEI", "next_generation_managed_by": "Data stream lifecycle", "prefer_ilm": false, "hidden": false, "system": false, "allow_custom_routing": false, "replicated": false }] } ``` --- docs/changelog/99947.yaml | 5 + .../change-mappings-and-settings.asciidoc | 10 +- .../data-streams/downsampling-manual.asciidoc | 6 +- .../indices/get-data-stream.asciidoc | 21 +- .../datastreams/DataStreamIT.java | 10 +- .../action/GetDataStreamsTransportAction.java | 42 ++- .../action/GetDataStreamsResponseTests.java | 240 +++++++++++++++++- .../test/data_stream/10_basic.yml | 71 ++++++ .../org/elasticsearch/TransportVersions.java | 3 +- .../datastreams/GetDataStreamAction.java | 132 +++++++++- .../java/org/elasticsearch/index/Index.java | 7 +- ...ataStreamAndIndexLifecycleMixingTests.java | 127 +++++++++ 12 files changed, 645 insertions(+), 29 deletions(-) create mode 100644 docs/changelog/99947.yaml diff --git a/docs/changelog/99947.yaml b/docs/changelog/99947.yaml new file mode 100644 index 0000000000000..61996c8fde92b --- /dev/null +++ b/docs/changelog/99947.yaml @@ -0,0 +1,5 @@ +pr: 99947 +summary: GET `_data_stream` displays both ILM and DSL information +area: Data streams +type: feature +issues: [] diff --git a/docs/reference/data-streams/change-mappings-and-settings.asciidoc b/docs/reference/data-streams/change-mappings-and-settings.asciidoc index 461addf65c53c..3922ef018a713 100644 --- a/docs/reference/data-streams/change-mappings-and-settings.asciidoc +++ b/docs/reference/data-streams/change-mappings-and-settings.asciidoc @@ -573,15 +573,21 @@ stream's oldest backing index. "indices": [ { "index_name": ".ds-my-data-stream-2099.03.07-000001", <1> - "index_uuid": "Gpdiyq8sRuK9WuthvAdFbw" + "index_uuid": "Gpdiyq8sRuK9WuthvAdFbw", + "prefer_ilm": true, + "managed_by": "Unmanaged" }, { "index_name": ".ds-my-data-stream-2099.03.08-000002", - "index_uuid": "_eEfRrFHS9OyhqWntkgHAQ" + "index_uuid": "_eEfRrFHS9OyhqWntkgHAQ", + "prefer_ilm": true, + "managed_by": "Unmanaged" } ], "generation": 2, "status": "GREEN", + "next_generation_managed_by": "Unmanaged", + "prefer_ilm": true, "template": "my-data-stream-template", "hidden": false, "system": false, diff --git a/docs/reference/data-streams/downsampling-manual.asciidoc b/docs/reference/data-streams/downsampling-manual.asciidoc index 6b98816c2cf56..cc74e98b258de 100644 --- a/docs/reference/data-streams/downsampling-manual.asciidoc +++ b/docs/reference/data-streams/downsampling-manual.asciidoc @@ -358,11 +358,15 @@ This returns: "indices": [ { "index_name": ".ds-my-data-stream-2023.07.26-000001", <1> - "index_uuid": "ltOJGmqgTVm4T-Buoe7Acg" + "index_uuid": "ltOJGmqgTVm4T-Buoe7Acg", + "prefer_ilm": true, + "managed_by": "Data stream lifecycle" } ], "generation": 1, "status": "GREEN", + "next_generation_managed_by": "Data stream lifecycle", + "prefer_ilm": true, "template": "my-data-stream-template", "hidden": false, "system": false, diff --git a/docs/reference/indices/get-data-stream.asciidoc b/docs/reference/indices/get-data-stream.asciidoc index ef2cf7eeee946..36998e7aa5fa3 100644 --- a/docs/reference/indices/get-data-stream.asciidoc +++ b/docs/reference/indices/get-data-stream.asciidoc @@ -225,7 +225,7 @@ cluster can not write into this data stream or change its mappings. `lifecycle`:: (object) -Functionality in preview:[]. Contains the configuration for the data stream lifecycle management of this data stream. +Contains the configuration for the data stream lifecycle management of this data stream. + .Properties of `lifecycle` [%collapsible%open] @@ -265,11 +265,17 @@ The API returns the following response: "indices": [ { "index_name": ".ds-my-data-stream-2099.03.07-000001", - "index_uuid": "xCEhwsp8Tey0-FLNFYVwSg" + "index_uuid": "xCEhwsp8Tey0-FLNFYVwSg", + "prefer_ilm": true, + "ilm_policy": "my-lifecycle-policy", + "managed_by": "Index Lifecycle Management" }, { "index_name": ".ds-my-data-stream-2099.03.08-000002", - "index_uuid": "PA_JquKGSiKcAKBA8DJ5gw" + "index_uuid": "PA_JquKGSiKcAKBA8DJ5gw", + "prefer_ilm": true, + "ilm_policy": "my-lifecycle-policy", + "managed_by": "Index Lifecycle Management" } ], "generation": 2, @@ -277,6 +283,8 @@ The API returns the following response: "my-meta-field": "foo" }, "status": "GREEN", + "next_generation_managed_by": "Index Lifecycle Management", + "prefer_ilm": true, "template": "my-index-template", "ilm_policy": "my-lifecycle-policy", "hidden": false, @@ -292,7 +300,10 @@ The API returns the following response: "indices": [ { "index_name": ".ds-my-data-stream-two-2099.03.08-000001", - "index_uuid": "3liBu2SYS5axasRt6fUIpA" + "index_uuid": "3liBu2SYS5axasRt6fUIpA", + "prefer_ilm": true, + "ilm_policy": "my-lifecycle-policy", + "managed_by": "Index Lifecycle Management" } ], "generation": 1, @@ -300,6 +311,8 @@ The API returns the following response: "my-meta-field": "foo" }, "status": "YELLOW", + "next_generation_managed_by": "Index Lifecycle Management", + "prefer_ilm": true, "template": "my-index-template", "ilm_policy": "my-lifecycle-policy", "hidden": false, diff --git a/modules/data-streams/src/internalClusterTest/java/org/elasticsearch/datastreams/DataStreamIT.java b/modules/data-streams/src/internalClusterTest/java/org/elasticsearch/datastreams/DataStreamIT.java index 384970bdc7ab9..bd4a449f640ce 100644 --- a/modules/data-streams/src/internalClusterTest/java/org/elasticsearch/datastreams/DataStreamIT.java +++ b/modules/data-streams/src/internalClusterTest/java/org/elasticsearch/datastreams/DataStreamIT.java @@ -1320,11 +1320,17 @@ public void testGetDataStream() throws Exception { ).actionGet(); assertThat(response.getDataStreams().size(), is(1)); DataStreamInfo metricsFooDataStream = response.getDataStreams().get(0); - assertThat(metricsFooDataStream.getDataStream().getName(), is("metrics-foo")); + DataStream dataStream = metricsFooDataStream.getDataStream(); + assertThat(dataStream.getName(), is("metrics-foo")); assertThat(metricsFooDataStream.getDataStreamStatus(), is(ClusterHealthStatus.YELLOW)); assertThat(metricsFooDataStream.getIndexTemplate(), is("template_for_foo")); assertThat(metricsFooDataStream.getIlmPolicy(), is(nullValue())); - assertThat(metricsFooDataStream.getDataStream().getLifecycle(), is(lifecycle)); + assertThat(dataStream.getLifecycle(), is(lifecycle)); + assertThat(metricsFooDataStream.templatePreferIlmValue(), is(true)); + GetDataStreamAction.Response.IndexProperties indexProperties = metricsFooDataStream.getIndexSettingsValues() + .get(dataStream.getWriteIndex()); + assertThat(indexProperties.ilmPolicyName(), is(nullValue())); + assertThat(indexProperties.preferIlm(), is(true)); } private static void assertBackingIndex(String backingIndex, String timestampFieldPathInMapping, Map expectedMapping) { diff --git a/modules/data-streams/src/main/java/org/elasticsearch/datastreams/action/GetDataStreamsTransportAction.java b/modules/data-streams/src/main/java/org/elasticsearch/datastreams/action/GetDataStreamsTransportAction.java index 73af952af524d..de81ca9bef18c 100644 --- a/modules/data-streams/src/main/java/org/elasticsearch/datastreams/action/GetDataStreamsTransportAction.java +++ b/modules/data-streams/src/main/java/org/elasticsearch/datastreams/action/GetDataStreamsTransportAction.java @@ -11,6 +11,8 @@ import org.apache.logging.log4j.Logger; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.datastreams.GetDataStreamAction; +import org.elasticsearch.action.datastreams.GetDataStreamAction.Response.IndexProperties; +import org.elasticsearch.action.datastreams.GetDataStreamAction.Response.ManagedBy; import org.elasticsearch.action.support.ActionFilters; import org.elasticsearch.action.support.master.TransportMasterNodeReadAction; import org.elasticsearch.cluster.ClusterState; @@ -21,6 +23,7 @@ import org.elasticsearch.cluster.metadata.DataStreamLifecycle; import org.elasticsearch.cluster.metadata.IndexMetadata; import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; +import org.elasticsearch.cluster.metadata.Metadata; import org.elasticsearch.cluster.metadata.MetadataIndexTemplateService; import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.inject.Inject; @@ -39,9 +42,12 @@ import java.time.Instant; import java.util.ArrayList; import java.util.Comparator; +import java.util.HashMap; import java.util.List; import java.util.Map; +import static org.elasticsearch.index.IndexSettings.PREFER_ILM_SETTING; + public class GetDataStreamsTransportAction extends TransportMasterNodeReadAction< GetDataStreamAction.Request, GetDataStreamAction.Response> { @@ -95,6 +101,7 @@ static GetDataStreamAction.Response innerOperation( List dataStreamInfos = new ArrayList<>(dataStreams.size()); for (DataStream dataStream : dataStreams) { final String indexTemplate; + boolean indexTemplatePreferIlmValue = true; String ilmPolicyName = null; if (dataStream.isSystem()) { SystemDataStreamDescriptor dataStreamDescriptor = systemIndices.findMatchingDataStreamDescriptor(dataStream.getName()); @@ -104,13 +111,15 @@ static GetDataStreamAction.Response innerOperation( dataStreamDescriptor.getComposableIndexTemplate(), dataStreamDescriptor.getComponentTemplates() ); - ilmPolicyName = settings.get("index.lifecycle.name"); + ilmPolicyName = settings.get(IndexMetadata.LIFECYCLE_NAME); + indexTemplatePreferIlmValue = PREFER_ILM_SETTING.get(settings); } } else { indexTemplate = MetadataIndexTemplateService.findV2Template(state.metadata(), dataStream.getName(), false); if (indexTemplate != null) { Settings settings = MetadataIndexTemplateService.resolveSettings(state.metadata(), indexTemplate); - ilmPolicyName = settings.get("index.lifecycle.name"); + ilmPolicyName = settings.get(IndexMetadata.LIFECYCLE_NAME); + indexTemplatePreferIlmValue = PREFER_ILM_SETTING.get(settings); } else { LOGGER.warn( "couldn't find any matching template for data stream [{}]. has it been restored (and possibly renamed)" @@ -125,18 +134,35 @@ static GetDataStreamAction.Response innerOperation( dataStream.getIndices().stream().map(Index::getName).toArray(String[]::new) ); + Map backingIndicesSettingsValues = new HashMap<>(); + Metadata metadata = state.getMetadata(); + for (Index index : dataStream.getIndices()) { + IndexMetadata indexMetadata = metadata.index(index); + Boolean preferIlm = PREFER_ILM_SETTING.get(indexMetadata.getSettings()); + assert preferIlm != null : "must use the default prefer ilm setting value, if nothing else"; + ManagedBy managedBy; + if (metadata.isIndexManagedByILM(indexMetadata)) { + managedBy = ManagedBy.ILM; + } else if (dataStream.isIndexManagedByDataStreamLifecycle(index, metadata::index)) { + managedBy = ManagedBy.LIFECYCLE; + } else { + managedBy = ManagedBy.UNMANAGED; + } + backingIndicesSettingsValues.put(index, new IndexProperties(preferIlm, indexMetadata.getLifecyclePolicyName(), managedBy)); + } + GetDataStreamAction.Response.TimeSeries timeSeries = null; if (dataStream.getIndexMode() == IndexMode.TIME_SERIES) { List> ranges = new ArrayList<>(); Tuple current = null; String previousIndexName = null; for (Index index : dataStream.getIndices()) { - IndexMetadata metadata = state.getMetadata().index(index); - if (metadata.getIndexMode() != IndexMode.TIME_SERIES) { + IndexMetadata indexMetadata = metadata.index(index); + if (indexMetadata.getIndexMode() != IndexMode.TIME_SERIES) { continue; } - Instant start = metadata.getTimeSeriesStart(); - Instant end = metadata.getTimeSeriesEnd(); + Instant start = indexMetadata.getTimeSeriesStart(); + Instant end = indexMetadata.getTimeSeriesEnd(); if (current == null) { current = new Tuple<>(start, end); } else if (current.v2().compareTo(start) == 0) { @@ -175,7 +201,9 @@ static GetDataStreamAction.Response innerOperation( streamHealth.getStatus(), indexTemplate, ilmPolicyName, - timeSeries + timeSeries, + backingIndicesSettingsValues, + indexTemplatePreferIlmValue ) ); } diff --git a/modules/data-streams/src/test/java/org/elasticsearch/datastreams/action/GetDataStreamsResponseTests.java b/modules/data-streams/src/test/java/org/elasticsearch/datastreams/action/GetDataStreamsResponseTests.java index 469c72e539c45..12e1604d10c1f 100644 --- a/modules/data-streams/src/test/java/org/elasticsearch/datastreams/action/GetDataStreamsResponseTests.java +++ b/modules/data-streams/src/test/java/org/elasticsearch/datastreams/action/GetDataStreamsResponseTests.java @@ -8,15 +8,33 @@ package org.elasticsearch.datastreams.action; import org.elasticsearch.action.datastreams.GetDataStreamAction.Response; +import org.elasticsearch.action.datastreams.GetDataStreamAction.Response.ManagedBy; import org.elasticsearch.cluster.health.ClusterHealthStatus; +import org.elasticsearch.cluster.metadata.DataStream; +import org.elasticsearch.cluster.metadata.DataStreamLifecycle; import org.elasticsearch.cluster.metadata.DataStreamTestHelper; +import org.elasticsearch.common.UUIDs; +import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.io.stream.Writeable; import org.elasticsearch.core.Tuple; +import org.elasticsearch.index.Index; +import org.elasticsearch.index.IndexMode; import org.elasticsearch.test.AbstractWireSerializingTestCase; +import org.elasticsearch.xcontent.ToXContent; +import org.elasticsearch.xcontent.XContentBuilder; +import org.elasticsearch.xcontent.XContentFactory; +import org.elasticsearch.xcontent.XContentParser; +import org.elasticsearch.xcontent.json.JsonXContent; import java.time.Instant; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; + +import static org.elasticsearch.cluster.metadata.DataStream.getDefaultBackingIndexName; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.nullValue; public class GetDataStreamsResponseTests extends AbstractWireSerializingTestCase { @@ -43,13 +61,198 @@ protected Response mutateInstance(Response instance) { return new Response(instance.getDataStreams().stream().map(this::mutateInstance).toList()); } + @SuppressWarnings("unchecked") + public void testResponseIlmAndDataStreamLifecycleRepresentation() throws Exception { + // we'll test a data stream with 3 backing indices - two managed by ILM (having the ILM policy configured for them) + // and one without any ILM policy configured + String dataStreamName = "logs"; + + Index firstGenerationIndex = new Index(getDefaultBackingIndexName(dataStreamName, 1), UUIDs.base64UUID()); + Index secondGenerationIndex = new Index(getDefaultBackingIndexName(dataStreamName, 2), UUIDs.base64UUID()); + Index writeIndex = new Index(getDefaultBackingIndexName(dataStreamName, 3), UUIDs.base64UUID()); + List indices = List.of(firstGenerationIndex, secondGenerationIndex, writeIndex); + { + // data stream has an enabled lifecycle + DataStream logs = new DataStream( + "logs", + indices, + 3, + null, + false, + false, + false, + true, + IndexMode.STANDARD, + new DataStreamLifecycle() + ); + + String ilmPolicyName = "rollover-30days"; + Map indexSettingsValues = Map.of( + firstGenerationIndex, + new Response.IndexProperties(true, ilmPolicyName, ManagedBy.ILM), + secondGenerationIndex, + new Response.IndexProperties(false, ilmPolicyName, ManagedBy.LIFECYCLE), + writeIndex, + new Response.IndexProperties(false, null, ManagedBy.LIFECYCLE) + ); + + Response.DataStreamInfo dataStreamInfo = new Response.DataStreamInfo( + logs, + ClusterHealthStatus.GREEN, + "index-template", + null, + null, + indexSettingsValues, + false + ); + Response response = new Response(List.of(dataStreamInfo)); + XContentBuilder contentBuilder = XContentFactory.jsonBuilder(); + response.toXContent(contentBuilder, ToXContent.EMPTY_PARAMS); + + BytesReference bytes = BytesReference.bytes(contentBuilder); + try (XContentParser parser = createParser(JsonXContent.jsonXContent, bytes)) { + Map map = parser.map(); + List dataStreams = (List) map.get(Response.DATA_STREAMS_FIELD.getPreferredName()); + assertThat(dataStreams.size(), is(1)); + Map dataStreamMap = (Map) dataStreams.get(0); + assertThat(dataStreamMap.get(DataStream.NAME_FIELD.getPreferredName()), is(dataStreamName)); + + assertThat(dataStreamMap.get(Response.DataStreamInfo.PREFER_ILM.getPreferredName()), is(false)); + assertThat(dataStreamMap.get(Response.DataStreamInfo.ILM_POLICY_FIELD.getPreferredName()), is(nullValue())); + assertThat(dataStreamMap.get(Response.DataStreamInfo.LIFECYCLE_FIELD.getPreferredName()), is(Map.of("enabled", true))); + assertThat( + dataStreamMap.get(Response.DataStreamInfo.NEXT_GENERATION_INDEX_MANAGED_BY.getPreferredName()), + is(ManagedBy.LIFECYCLE.displayValue) + ); + + List indicesRepresentation = (List) dataStreamMap.get(DataStream.INDICES_FIELD.getPreferredName()); + Map firstGenIndexRepresentation = (Map) indicesRepresentation.get(0); + assertThat(firstGenIndexRepresentation.get("index_name"), is(firstGenerationIndex.getName())); + assertThat(firstGenIndexRepresentation.get(Response.DataStreamInfo.PREFER_ILM.getPreferredName()), is(true)); + assertThat(firstGenIndexRepresentation.get(Response.DataStreamInfo.ILM_POLICY_FIELD.getPreferredName()), is(ilmPolicyName)); + assertThat( + firstGenIndexRepresentation.get(Response.DataStreamInfo.MANAGED_BY.getPreferredName()), + is(ManagedBy.ILM.displayValue) + ); + + Map secondGenIndexRepresentation = (Map) indicesRepresentation.get(1); + assertThat(secondGenIndexRepresentation.get("index_name"), is(secondGenerationIndex.getName())); + assertThat(secondGenIndexRepresentation.get(Response.DataStreamInfo.PREFER_ILM.getPreferredName()), is(false)); + assertThat( + secondGenIndexRepresentation.get(Response.DataStreamInfo.ILM_POLICY_FIELD.getPreferredName()), + is(ilmPolicyName) + ); + assertThat( + secondGenIndexRepresentation.get(Response.DataStreamInfo.MANAGED_BY.getPreferredName()), + is(ManagedBy.LIFECYCLE.displayValue) + ); + + // the write index is managed by data stream lifecycle + Map writeIndexRepresentation = (Map) indicesRepresentation.get(2); + assertThat(writeIndexRepresentation.get("index_name"), is(writeIndex.getName())); + assertThat(writeIndexRepresentation.get(Response.DataStreamInfo.PREFER_ILM.getPreferredName()), is(false)); + assertThat(writeIndexRepresentation.get(Response.DataStreamInfo.ILM_POLICY_FIELD.getPreferredName()), is(nullValue())); + assertThat( + writeIndexRepresentation.get(Response.DataStreamInfo.MANAGED_BY.getPreferredName()), + is(ManagedBy.LIFECYCLE.displayValue) + ); + } + } + + { + // data stream has a lifecycle that's not enabled + DataStream logs = new DataStream( + "logs", + indices, + 3, + null, + false, + false, + false, + true, + IndexMode.STANDARD, + new DataStreamLifecycle(null, null, false) + ); + + String ilmPolicyName = "rollover-30days"; + Map indexSettingsValues = Map.of( + firstGenerationIndex, + new Response.IndexProperties(true, ilmPolicyName, ManagedBy.ILM), + secondGenerationIndex, + new Response.IndexProperties(true, ilmPolicyName, ManagedBy.ILM), + writeIndex, + new Response.IndexProperties(false, null, ManagedBy.UNMANAGED) + ); + + Response.DataStreamInfo dataStreamInfo = new Response.DataStreamInfo( + logs, + ClusterHealthStatus.GREEN, + "index-template", + null, + null, + indexSettingsValues, + false + ); + Response response = new Response(List.of(dataStreamInfo)); + XContentBuilder contentBuilder = XContentFactory.jsonBuilder(); + response.toXContent(contentBuilder, ToXContent.EMPTY_PARAMS); + + BytesReference bytes = BytesReference.bytes(contentBuilder); + try (XContentParser parser = createParser(JsonXContent.jsonXContent, bytes)) { + Map map = parser.map(); + List dataStreams = (List) map.get(Response.DATA_STREAMS_FIELD.getPreferredName()); + assertThat(dataStreams.size(), is(1)); + Map dataStreamMap = (Map) dataStreams.get(0); + assertThat(dataStreamMap.get(DataStream.NAME_FIELD.getPreferredName()), is(dataStreamName)); + // note that the prefer_ilm value is displayed at the top level even if the template backing the data stream doesn't have a + // policy specified anymore + assertThat(dataStreamMap.get(Response.DataStreamInfo.PREFER_ILM.getPreferredName()), is(false)); + assertThat(dataStreamMap.get(Response.DataStreamInfo.ILM_POLICY_FIELD.getPreferredName()), is(nullValue())); + assertThat(dataStreamMap.get(Response.DataStreamInfo.LIFECYCLE_FIELD.getPreferredName()), is(Map.of("enabled", false))); + assertThat( + dataStreamMap.get(Response.DataStreamInfo.NEXT_GENERATION_INDEX_MANAGED_BY.getPreferredName()), + is(ManagedBy.UNMANAGED.displayValue) + ); + + List indicesRepresentation = (List) dataStreamMap.get(DataStream.INDICES_FIELD.getPreferredName()); + Map firstGenIndexRepresentation = (Map) indicesRepresentation.get(0); + assertThat(firstGenIndexRepresentation.get("index_name"), is(firstGenerationIndex.getName())); + assertThat(firstGenIndexRepresentation.get(Response.DataStreamInfo.PREFER_ILM.getPreferredName()), is(true)); + assertThat(firstGenIndexRepresentation.get(Response.DataStreamInfo.ILM_POLICY_FIELD.getPreferredName()), is(ilmPolicyName)); + assertThat( + firstGenIndexRepresentation.get(Response.DataStreamInfo.MANAGED_BY.getPreferredName()), + is(ManagedBy.ILM.displayValue) + ); + + // the write index is managed by data stream lifecycle + Map writeIndexRepresentation = (Map) indicesRepresentation.get(2); + assertThat(writeIndexRepresentation.get("index_name"), is(writeIndex.getName())); + assertThat(writeIndexRepresentation.get(Response.DataStreamInfo.PREFER_ILM.getPreferredName()), is(false)); + assertThat(writeIndexRepresentation.get(Response.DataStreamInfo.ILM_POLICY_FIELD.getPreferredName()), is(nullValue())); + assertThat( + writeIndexRepresentation.get(Response.DataStreamInfo.MANAGED_BY.getPreferredName()), + is(ManagedBy.UNMANAGED.displayValue) + ); + } + } + } + + public void testManagedByDisplayValuesDontAccidentalyChange() { + // UI might derive logic based on the display values so any changes should be coordinated with the UI team + assertThat(ManagedBy.ILM.displayValue, is("Index Lifecycle Management")); + assertThat(ManagedBy.LIFECYCLE.displayValue, is("Data stream lifecycle")); + assertThat(ManagedBy.UNMANAGED.displayValue, is("Unmanaged")); + } + private Response.DataStreamInfo mutateInstance(Response.DataStreamInfo instance) { var dataStream = instance.getDataStream(); var status = instance.getDataStreamStatus(); var indexTemplate = instance.getIndexTemplate(); var ilmPolicyName = instance.getIlmPolicy(); var timeSeries = instance.getTimeSeries(); - switch (randomIntBetween(0, 4)) { + var indexSettings = instance.getIndexSettingsValues(); + var templatePreferIlm = instance.templatePreferIlmValue(); + switch (randomIntBetween(0, 6)) { case 0 -> dataStream = randomValueOtherThan(dataStream, DataStreamTestHelper::randomInstance); case 1 -> status = randomValueOtherThan(status, () -> randomFrom(ClusterHealthStatus.values())); case 2 -> indexTemplate = randomBoolean() && indexTemplate != null ? null : randomAlphaOfLengthBetween(2, 10); @@ -57,8 +260,22 @@ private Response.DataStreamInfo mutateInstance(Response.DataStreamInfo instance) case 4 -> timeSeries = randomBoolean() && timeSeries != null ? null : randomValueOtherThan(timeSeries, () -> new Response.TimeSeries(generateRandomTimeSeries())); + case 5 -> indexSettings = randomValueOtherThan( + indexSettings, + () -> randomBoolean() + ? Map.of() + : Map.of( + new Index(randomAlphaOfLengthBetween(50, 100), UUIDs.base64UUID()), + new Response.IndexProperties( + randomBoolean(), + randomAlphaOfLengthBetween(50, 100), + randomBoolean() ? ManagedBy.ILM : ManagedBy.LIFECYCLE + ) + ) + ); + case 6 -> templatePreferIlm = templatePreferIlm ? false : true; } - return new Response.DataStreamInfo(dataStream, status, indexTemplate, ilmPolicyName, timeSeries); + return new Response.DataStreamInfo(dataStream, status, indexTemplate, ilmPolicyName, timeSeries, indexSettings, templatePreferIlm); } private List> generateRandomTimeSeries() { @@ -70,6 +287,21 @@ private List> generateRandomTimeSeries() { return timeSeries; } + private Map generateRandomIndexSettingsValues() { + Map values = new HashMap<>(); + for (int i = 0; i < randomIntBetween(0, 3); i++) { + values.put( + new Index(randomAlphaOfLengthBetween(50, 100), UUIDs.base64UUID()), + new Response.IndexProperties( + randomBoolean(), + randomAlphaOfLengthBetween(50, 100), + randomBoolean() ? ManagedBy.ILM : ManagedBy.LIFECYCLE + ) + ); + } + return values; + } + private Response.DataStreamInfo generateRandomDataStreamInfo() { List> timeSeries = randomBoolean() ? generateRandomTimeSeries() : null; return new Response.DataStreamInfo( @@ -77,7 +309,9 @@ private Response.DataStreamInfo generateRandomDataStreamInfo() { ClusterHealthStatus.GREEN, randomAlphaOfLengthBetween(2, 10), randomAlphaOfLengthBetween(2, 10), - timeSeries != null ? new Response.TimeSeries(timeSeries) : null + timeSeries != null ? new Response.TimeSeries(timeSeries) : null, + generateRandomIndexSettingsValues(), + randomBoolean() ); } } diff --git a/modules/data-streams/src/yamlRestTest/resources/rest-api-spec/test/data_stream/10_basic.yml b/modules/data-streams/src/yamlRestTest/resources/rest-api-spec/test/data_stream/10_basic.yml index 50c8e2c74dc74..09cec438d10cc 100644 --- a/modules/data-streams/src/yamlRestTest/resources/rest-api-spec/test/data_stream/10_basic.yml +++ b/modules/data-streams/src/yamlRestTest/resources/rest-api-spec/test/data_stream/10_basic.yml @@ -311,6 +311,77 @@ setup: name: simple-data-stream2 - is_true: acknowledged +--- +"Get data stream and check DSL and ILM information": + - skip: + version: " - 8.10.99" + reason: "data streams DSL and ILM mixing information available in 8.11+" + + - do: + allowed_warnings: + - "index template [mixing-dsl-template] has index patterns [mixing-dsl-stream] matching patterns from existing older templates + [global] with patterns (global => [*]); this template [mixing-dsl-template] will take precedence during new index creation" + indices.put_index_template: + name: mixing-dsl-template + body: + index_patterns: [mixing-dsl-stream] + template: + mappings: + properties: + '@timestamp': + type: date_nanos + lifecycle: + data_retention: "30d" + enabled: false + settings: + index.lifecycle.prefer_ilm: false + index.lifecycle.name: "missing_ilm_policy" + data_stream: {} + + - do: + indices.create_data_stream: + name: mixing-dsl-stream + - is_true: acknowledged + + - do: + indices.get_data_stream: + name: mixing-dsl-stream + - match: { data_streams.0.name: mixing-dsl-stream } + - match: { data_streams.0.timestamp_field.name: '@timestamp' } + - match: { data_streams.0.generation: 1 } + - match: { data_streams.0.ilm_policy: "missing_ilm_policy" } + - match: { data_streams.0.prefer_ilm: false } + - match: { data_streams.0.next_generation_managed_by: "Index Lifecycle Management" } + - length: { data_streams.0.indices: 1 } + - match: { data_streams.0.indices.0.prefer_ilm: false } + - match: { data_streams.0.indices.0.ilm_policy: "missing_ilm_policy" } + - match: { data_streams.0.indices.0.managed_by: "Index Lifecycle Management" } + + - do: + indices.put_data_lifecycle: + name: "*" + body: > + { + "data_retention": "30d", + "enabled": true + } + + - is_true: acknowledged + + - do: + indices.get_data_stream: + name: mixing-dsl-stream + - match: { data_streams.0.name: mixing-dsl-stream } + - match: { data_streams.0.timestamp_field.name: '@timestamp' } + - match: { data_streams.0.generation: 1 } + - match: { data_streams.0.ilm_policy: "missing_ilm_policy" } + - match: { data_streams.0.prefer_ilm: false } + - match: { data_streams.0.next_generation_managed_by: "Data stream lifecycle" } + - length: { data_streams.0.indices: 1 } + - match: { data_streams.0.indices.0.prefer_ilm: false } + - match: { data_streams.0.indices.0.ilm_policy: "missing_ilm_policy" } + - match: { data_streams.0.indices.0.managed_by: "Data stream lifecycle" } + --- "Delete data stream with backing indices": - skip: diff --git a/server/src/main/java/org/elasticsearch/TransportVersions.java b/server/src/main/java/org/elasticsearch/TransportVersions.java index 5f120134acb04..360af92d6aa43 100644 --- a/server/src/main/java/org/elasticsearch/TransportVersions.java +++ b/server/src/main/java/org/elasticsearch/TransportVersions.java @@ -145,8 +145,9 @@ static TransportVersion def(int id) { public static final TransportVersion WAIT_FOR_CLUSTER_STATE_IN_RECOVERY_ADDED = def(8_502_00_0); public static final TransportVersion RECOVERY_COMMIT_TOO_NEW_EXCEPTION_ADDED = def(8_503_00_0); public static final TransportVersion NODE_INFO_COMPONENT_VERSIONS_ADDED = def(8_504_00_0); - public static final TransportVersion COMPACT_FIELD_CAPS_ADDED = def(8_505_00_0); + public static final TransportVersion DATA_STREAM_RESPONSE_INDEX_PROPERTIES = def(8_506_00_0); + /* * STOP! READ THIS FIRST! No, really, * ____ _____ ___ ____ _ ____ _____ _ ____ _____ _ _ ___ ____ _____ ___ ____ ____ _____ _ diff --git a/server/src/main/java/org/elasticsearch/action/datastreams/GetDataStreamAction.java b/server/src/main/java/org/elasticsearch/action/datastreams/GetDataStreamAction.java index aa69ede54dea1..9c1fb63a6b8d0 100644 --- a/server/src/main/java/org/elasticsearch/action/datastreams/GetDataStreamAction.java +++ b/server/src/main/java/org/elasticsearch/action/datastreams/GetDataStreamAction.java @@ -23,6 +23,7 @@ import org.elasticsearch.common.io.stream.Writeable; import org.elasticsearch.core.Nullable; import org.elasticsearch.core.Tuple; +import org.elasticsearch.index.Index; import org.elasticsearch.index.mapper.DateFieldMapper; import org.elasticsearch.xcontent.ParseField; import org.elasticsearch.xcontent.ToXContentObject; @@ -32,8 +33,11 @@ import java.time.Instant; import java.util.Arrays; import java.util.List; +import java.util.Map; import java.util.Objects; +import static org.elasticsearch.TransportVersions.DATA_STREAM_RESPONSE_INDEX_PROPERTIES; + public class GetDataStreamAction extends ActionType { public static final GetDataStreamAction INSTANCE = new GetDataStreamAction(); @@ -142,12 +146,28 @@ public Request includeDefaults(boolean includeDefaults) { } public static class Response extends ActionResponse implements ToXContentObject { + + public enum ManagedBy { + ILM("Index Lifecycle Management"), + LIFECYCLE("Data stream lifecycle"), + UNMANAGED("Unmanaged"); + + public final String displayValue; + + ManagedBy(String displayValue) { + this.displayValue = displayValue; + } + } + public static final ParseField DATA_STREAMS_FIELD = new ParseField("data_streams"); public static class DataStreamInfo implements SimpleDiffable, ToXContentObject { public static final ParseField STATUS_FIELD = new ParseField("status"); public static final ParseField INDEX_TEMPLATE_FIELD = new ParseField("template"); + public static final ParseField PREFER_ILM = new ParseField("prefer_ilm"); + public static final ParseField MANAGED_BY = new ParseField("managed_by"); + public static final ParseField NEXT_GENERATION_INDEX_MANAGED_BY = new ParseField("next_generation_managed_by"); public static final ParseField ILM_POLICY_FIELD = new ParseField("ilm_policy"); public static final ParseField LIFECYCLE_FIELD = new ParseField("lifecycle"); public static final ParseField HIDDEN_FIELD = new ParseField("hidden"); @@ -167,28 +187,39 @@ public static class DataStreamInfo implements SimpleDiffable, To private final String ilmPolicyName; @Nullable private final TimeSeries timeSeries; + private final Map indexSettingsValues; + private final boolean templatePreferIlmValue; public DataStreamInfo( DataStream dataStream, ClusterHealthStatus dataStreamStatus, @Nullable String indexTemplate, @Nullable String ilmPolicyName, - @Nullable TimeSeries timeSeries + @Nullable TimeSeries timeSeries, + Map indexSettingsValues, + boolean templatePreferIlmValue ) { this.dataStream = dataStream; this.dataStreamStatus = dataStreamStatus; this.indexTemplate = indexTemplate; this.ilmPolicyName = ilmPolicyName; this.timeSeries = timeSeries; + this.indexSettingsValues = indexSettingsValues; + this.templatePreferIlmValue = templatePreferIlmValue; } + @SuppressWarnings("unchecked") DataStreamInfo(StreamInput in) throws IOException { this( new DataStream(in), ClusterHealthStatus.readFrom(in), in.readOptionalString(), in.readOptionalString(), - in.getTransportVersion().onOrAfter(TransportVersions.V_8_3_0) ? in.readOptionalWriteable(TimeSeries::new) : null + in.getTransportVersion().onOrAfter(TransportVersions.V_8_3_0) ? in.readOptionalWriteable(TimeSeries::new) : null, + in.getTransportVersion().onOrAfter(DATA_STREAM_RESPONSE_INDEX_PROPERTIES) + ? in.readMap(Index::new, IndexProperties::new) + : Map.of(), + in.getTransportVersion().onOrAfter(DATA_STREAM_RESPONSE_INDEX_PROPERTIES) ? in.readBoolean() : true ); } @@ -215,6 +246,14 @@ public TimeSeries getTimeSeries() { return timeSeries; } + public Map getIndexSettingsValues() { + return indexSettingsValues; + } + + public boolean templatePreferIlmValue() { + return templatePreferIlmValue; + } + @Override public void writeTo(StreamOutput out) throws IOException { dataStream.writeTo(out); @@ -224,6 +263,10 @@ public void writeTo(StreamOutput out) throws IOException { if (out.getTransportVersion().onOrAfter(TransportVersions.V_8_3_0)) { out.writeOptionalWriteable(timeSeries); } + if (out.getTransportVersion().onOrAfter(DATA_STREAM_RESPONSE_INDEX_PROPERTIES)) { + out.writeMap(indexSettingsValues); + out.writeBoolean(templatePreferIlmValue); + } } @Override @@ -242,7 +285,27 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params, @Nulla .startObject() .field(DataStream.NAME_FIELD.getPreferredName(), DataStream.TIMESTAMP_FIELD_NAME) .endObject(); - builder.xContentList(DataStream.INDICES_FIELD.getPreferredName(), dataStream.getIndices()); + + builder.field(DataStream.INDICES_FIELD.getPreferredName()); + if (dataStream.getIndices() == null) { + builder.nullValue(); + } else { + builder.startArray(); + for (Index index : dataStream.getIndices()) { + builder.startObject(); + index.toXContentFragment(builder); + IndexProperties indexProperties = indexSettingsValues.get(index); + if (indexProperties != null) { + builder.field(PREFER_ILM.getPreferredName(), indexProperties.preferIlm()); + if (indexProperties.ilmPolicyName() != null) { + builder.field(ILM_POLICY_FIELD.getPreferredName(), indexProperties.ilmPolicyName()); + } + builder.field(MANAGED_BY.getPreferredName(), indexProperties.managedBy.displayValue); + } + builder.endObject(); + } + builder.endArray(); + } builder.field(DataStream.GENERATION_FIELD.getPreferredName(), dataStream.getGeneration()); if (dataStream.getMetadata() != null) { builder.field(DataStream.METADATA_FIELD.getPreferredName(), dataStream.getMetadata()); @@ -258,6 +321,8 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params, @Nulla if (ilmPolicyName != null) { builder.field(ILM_POLICY_FIELD.getPreferredName(), ilmPolicyName); } + builder.field(NEXT_GENERATION_INDEX_MANAGED_BY.getPreferredName(), getNextGenerationManagedBy().displayValue); + builder.field(PREFER_ILM.getPreferredName(), templatePreferIlmValue); builder.field(HIDDEN_FIELD.getPreferredName(), dataStream.isHidden()); builder.field(SYSTEM_FIELD.getPreferredName(), dataStream.isSystem()); builder.field(ALLOW_CUSTOM_ROUTING.getPreferredName(), dataStream.isAllowCustomRouting()); @@ -280,21 +345,55 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params, @Nulla return builder; } + /** + * Computes and returns which system will manage the next generation for this data stream. + */ + public ManagedBy getNextGenerationManagedBy() { + // both ILM and DSL are configured so let's check the prefer_ilm setting to see which system takes precedence + if (ilmPolicyName != null && dataStream.getLifecycle() != null && dataStream.getLifecycle().isEnabled()) { + return templatePreferIlmValue ? ManagedBy.ILM : ManagedBy.LIFECYCLE; + } + + if (ilmPolicyName != null) { + return ManagedBy.ILM; + } + + if (dataStream.getLifecycle() != null && dataStream.getLifecycle().isEnabled()) { + return ManagedBy.LIFECYCLE; + } + + return ManagedBy.UNMANAGED; + } + @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } DataStreamInfo that = (DataStreamInfo) o; - return dataStream.equals(that.dataStream) + return templatePreferIlmValue == that.templatePreferIlmValue + && Objects.equals(dataStream, that.dataStream) && dataStreamStatus == that.dataStreamStatus && Objects.equals(indexTemplate, that.indexTemplate) && Objects.equals(ilmPolicyName, that.ilmPolicyName) - && Objects.equals(timeSeries, that.timeSeries); + && Objects.equals(timeSeries, that.timeSeries) + && Objects.equals(indexSettingsValues, that.indexSettingsValues); } @Override public int hashCode() { - return Objects.hash(dataStream, dataStreamStatus, indexTemplate, ilmPolicyName, timeSeries); + return Objects.hash( + dataStream, + dataStreamStatus, + indexTemplate, + ilmPolicyName, + timeSeries, + indexSettingsValues, + templatePreferIlmValue + ); } } @@ -326,6 +425,23 @@ public int hashCode() { } } + /** + * Encapsulates the configured properties we want to display for each backing index. + * They'll usually be settings values, but could also be additional properties derived from settings. + */ + public record IndexProperties(boolean preferIlm, @Nullable String ilmPolicyName, ManagedBy managedBy) implements Writeable { + public IndexProperties(StreamInput in) throws IOException { + this(in.readBoolean(), in.readOptionalString(), in.readEnum(ManagedBy.class)); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeBoolean(preferIlm); + out.writeOptionalString(ilmPolicyName); + out.writeEnum(managedBy); + } + } + private final List dataStreams; @Nullable private final RolloverConfiguration rolloverConfiguration; diff --git a/server/src/main/java/org/elasticsearch/index/Index.java b/server/src/main/java/org/elasticsearch/index/Index.java index 85468326954d6..e11fd394d60a9 100644 --- a/server/src/main/java/org/elasticsearch/index/Index.java +++ b/server/src/main/java/org/elasticsearch/index/Index.java @@ -103,9 +103,14 @@ public void writeTo(final StreamOutput out) throws IOException { @Override public XContentBuilder toXContent(final XContentBuilder builder, final Params params) throws IOException { builder.startObject(); + toXContentFragment(builder); + return builder.endObject(); + } + + public XContentBuilder toXContentFragment(final XContentBuilder builder) throws IOException { builder.field(INDEX_NAME_KEY, name); builder.field(INDEX_UUID_KEY, uuid); - return builder.endObject(); + return builder; } public static Index fromXContent(final XContentParser parser) throws IOException { diff --git a/x-pack/plugin/ilm/src/internalClusterTest/java/org/elasticsearch/xpack/ilm/DataStreamAndIndexLifecycleMixingTests.java b/x-pack/plugin/ilm/src/internalClusterTest/java/org/elasticsearch/xpack/ilm/DataStreamAndIndexLifecycleMixingTests.java index 4086a1a729c14..de355cd675089 100644 --- a/x-pack/plugin/ilm/src/internalClusterTest/java/org/elasticsearch/xpack/ilm/DataStreamAndIndexLifecycleMixingTests.java +++ b/x-pack/plugin/ilm/src/internalClusterTest/java/org/elasticsearch/xpack/ilm/DataStreamAndIndexLifecycleMixingTests.java @@ -15,6 +15,7 @@ import org.elasticsearch.action.bulk.BulkResponse; import org.elasticsearch.action.datastreams.CreateDataStreamAction; import org.elasticsearch.action.datastreams.GetDataStreamAction; +import org.elasticsearch.action.datastreams.GetDataStreamAction.Response.ManagedBy; import org.elasticsearch.action.datastreams.lifecycle.ExplainIndexDataStreamLifecycle; import org.elasticsearch.action.index.IndexRequest; import org.elasticsearch.cluster.metadata.ComposableIndexTemplate; @@ -56,10 +57,13 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Optional; +import java.util.function.Function; import static org.elasticsearch.cluster.metadata.DataStreamTestHelper.backingIndexEqualTo; import static org.elasticsearch.cluster.metadata.MetadataIndexTemplateService.DEFAULT_TIMESTAMP_FIELD; import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked; +import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.nullValue; @@ -798,6 +802,129 @@ public void testUpdateIndexTemplateToMigrateFromDataStreamLifecycleToIlm() throw }); } + public void testGetDataStreamResponse() throws Exception { + // ILM rolls over every 2 documents + RolloverAction rolloverIlmAction = new RolloverAction(RolloverConditions.newBuilder().addMaxIndexDocsCondition(2L).build()); + Phase hotPhase = new Phase("hot", TimeValue.ZERO, Map.of(rolloverIlmAction.getWriteableName(), rolloverIlmAction)); + LifecyclePolicy lifecyclePolicy = new LifecyclePolicy(policy, Map.of("hot", hotPhase)); + PutLifecycleAction.Request putLifecycleRequest = new PutLifecycleAction.Request(lifecyclePolicy); + assertAcked(client().execute(PutLifecycleAction.INSTANCE, putLifecycleRequest).get()); + + putComposableIndexTemplate( + indexTemplateName, + null, + List.of(dataStreamName + "*"), + Settings.builder().put(LifecycleSettings.LIFECYCLE_NAME, policy).build(), + null, + null + ); + CreateDataStreamAction.Request createDataStreamRequest = new CreateDataStreamAction.Request(dataStreamName); + client().execute(CreateDataStreamAction.INSTANCE, createDataStreamRequest).get(); + + indexDocs(dataStreamName, 2); + + // wait to rollover + assertBusy(() -> { + GetDataStreamAction.Request getDataStreamRequest = new GetDataStreamAction.Request(new String[] { dataStreamName }); + GetDataStreamAction.Response getDataStreamResponse = client().execute(GetDataStreamAction.INSTANCE, getDataStreamRequest) + .actionGet(); + assertThat(getDataStreamResponse.getDataStreams().size(), equalTo(1)); + assertThat(getDataStreamResponse.getDataStreams().get(0).getDataStream().getIndices().size(), is(2)); + }); + + // prefer_ilm false in the index template + putComposableIndexTemplate( + indexTemplateName, + null, + List.of(dataStreamName + "*"), + Settings.builder().put(LifecycleSettings.LIFECYCLE_NAME, policy).put(IndexSettings.PREFER_ILM, false).build(), + null, + null + ); + + client().execute( + PutDataStreamLifecycleAction.INSTANCE, + new PutDataStreamLifecycleAction.Request(new String[] { dataStreamName }, TimeValue.timeValueDays(90)) + ).actionGet(); + + // rollover again - at this point this data stream should have 2 backing indices managed by ILM and the write index managed by + // data stream lifecycle + indexDocs(dataStreamName, 2); + + assertBusy(() -> { + GetDataStreamAction.Request getDataStreamRequest = new GetDataStreamAction.Request(new String[] { dataStreamName }); + GetDataStreamAction.Response getDataStreamResponse = client().execute(GetDataStreamAction.INSTANCE, getDataStreamRequest) + .actionGet(); + assertThat(getDataStreamResponse.getDataStreams().size(), equalTo(1)); + GetDataStreamAction.Response.DataStreamInfo dataStreamInfo = getDataStreamResponse.getDataStreams().get(0); + List indices = dataStreamInfo.getDataStream().getIndices(); + assertThat(indices.size(), is(3)); + + // the prefer_ilm value from the template should be reflected in the response at the top level + assertThat(dataStreamInfo.templatePreferIlmValue(), is(false)); + // the template ILM policy should still be reflected at the top level + assertThat(dataStreamInfo.getIlmPolicy(), is(policy)); + + List backingIndices = getBackingIndices(dataStreamName); + String firstGenerationIndex = backingIndices.get(0); + String secondGenerationIndex = backingIndices.get(1); + String writeIndex = backingIndices.get(2); + assertThat( + indices.stream().map(i -> i.getName()).toList(), + containsInAnyOrder(firstGenerationIndex, secondGenerationIndex, writeIndex) + ); + + Function> backingIndexSupplier = indexName -> indices.stream() + .filter(index -> index.getName().equals(indexName)) + .findFirst(); + + // let's assert the policy is reported for all indices (as it's present in the index template) and the value of the + // prefer_ilm setting remains true for the first 2 generations and is false for the write index (the generation after rollover) + Optional firstGenSettings = backingIndexSupplier.apply(firstGenerationIndex); + assertThat(firstGenSettings.isPresent(), is(true)); + assertThat(dataStreamInfo.getIndexSettingsValues().get(firstGenSettings.get()).preferIlm(), is(true)); + assertThat(dataStreamInfo.getIndexSettingsValues().get(firstGenSettings.get()).ilmPolicyName(), is(policy)); + assertThat(dataStreamInfo.getIndexSettingsValues().get(firstGenSettings.get()).managedBy(), is(ManagedBy.ILM)); + Optional secondGenSettings = backingIndexSupplier.apply(secondGenerationIndex); + assertThat(secondGenSettings.isPresent(), is(true)); + assertThat(dataStreamInfo.getIndexSettingsValues().get(secondGenSettings.get()).preferIlm(), is(true)); + assertThat(dataStreamInfo.getIndexSettingsValues().get(secondGenSettings.get()).ilmPolicyName(), is(policy)); + assertThat(dataStreamInfo.getIndexSettingsValues().get(secondGenSettings.get()).managedBy(), is(ManagedBy.ILM)); + Optional writeIndexSettings = backingIndexSupplier.apply(writeIndex); + assertThat(writeIndexSettings.isPresent(), is(true)); + assertThat(dataStreamInfo.getIndexSettingsValues().get(writeIndexSettings.get()).preferIlm(), is(false)); + assertThat(dataStreamInfo.getIndexSettingsValues().get(writeIndexSettings.get()).ilmPolicyName(), is(policy)); + assertThat(dataStreamInfo.getIndexSettingsValues().get(writeIndexSettings.get()).managedBy(), is(ManagedBy.LIFECYCLE)); + + // with the current configuratino, the next generation index will be managed by DSL + assertThat(dataStreamInfo.getNextGenerationManagedBy(), is(ManagedBy.LIFECYCLE)); + }); + + // remove ILM policy and prefer_ilm from template + putComposableIndexTemplate(indexTemplateName, null, List.of(dataStreamName + "*"), Settings.builder().build(), null, null); + GetDataStreamAction.Request getDataStreamRequest = new GetDataStreamAction.Request(new String[] { dataStreamName }); + GetDataStreamAction.Response getDataStreamResponse = client().execute(GetDataStreamAction.INSTANCE, getDataStreamRequest) + .actionGet(); + assertThat(getDataStreamResponse.getDataStreams().size(), equalTo(1)); + GetDataStreamAction.Response.DataStreamInfo dataStreamInfo = getDataStreamResponse.getDataStreams().get(0); + // since the ILM related settings are gone from the index template, this data stream should now be managed by lifecycle + assertThat(dataStreamInfo.getNextGenerationManagedBy(), is(ManagedBy.LIFECYCLE)); + + // disable data stream lifecycle on the data stream. the future generations will be UNMANAGED + client().execute( + PutDataStreamLifecycleAction.INSTANCE, + new PutDataStreamLifecycleAction.Request(new String[] { dataStreamName }, TimeValue.timeValueDays(90), false) + ).actionGet(); + + getDataStreamRequest = new GetDataStreamAction.Request(new String[] { dataStreamName }); + getDataStreamResponse = client().execute(GetDataStreamAction.INSTANCE, getDataStreamRequest).actionGet(); + assertThat(getDataStreamResponse.getDataStreams().size(), equalTo(1)); + dataStreamInfo = getDataStreamResponse.getDataStreams().get(0); + // since the ILM related settings are gone from the index template and the lifeclcye is disabled, this data stream should now be + // managed unmanaged + assertThat(dataStreamInfo.getNextGenerationManagedBy(), is(ManagedBy.UNMANAGED)); + } + static void indexDocs(String dataStream, int numDocs) { BulkRequest bulkRequest = new BulkRequest(); for (int i = 0; i < numDocs; i++) { From 7c21ce3f1b461c7a73abd90af58bf7ca53a2b1b2 Mon Sep 17 00:00:00 2001 From: Max Hniebergall <137079448+maxhniebergall@users.noreply.github.com> Date: Thu, 28 Sep 2023 13:56:45 -0400 Subject: [PATCH 18/32] Platform specific models (#99584) * Added platform architecture field to TrainedModelMetadata and users of TrainedModelMetadata * Added TransportVersions guarding for TrainedModelMetadata * Prevent platform-specific models from being deployed on the wrong architecture * Added logic to only verify node architectures for models which are platform specific * Handle null platform architecture * Added logging for the detection of heterogeneous platform architectures among ML nodes and refactoring to support this * Added platform architecture field to TrainedModelConfig * Stop platform-speficic model when rebalance occurs and the cluster has a heterogeneous architecture among ML nodes * Added logic to TransportPutTrainedModelAction to return a warning response header when the model is paltform-specific and cannot be depoloyed on the cluster at that time due to heterogenous architectures among ML nodes * Added MlPlatformArchitecturesUtilTests * Updated Create Trained Models API docs to describe the new platform_architecture optional field. * Updated/incremented InferenceIndexConstants * Added special override to make models with linux-x86_64 in the model ID to be platform specific --- docs/changelog/99584.yaml | 5 + .../apis/put-trained-models.asciidoc | 12 ++ .../org/elasticsearch/TransportVersions.java | 1 + .../core/ml/inference/TrainedModelConfig.java | 39 +++- .../persistence/InferenceIndexConstants.java | 8 +- .../ml/inference/TrainedModelConfigTests.java | 13 +- .../ml/inference_index_mappings.json | 3 + .../xpack/ml/MachineLearning.java | 3 +- .../TransportPutTrainedModelAction.java | 89 ++++++-- .../TrainedModelAssignmentClusterService.java | 159 ++++++++++---- .../deployment/DeploymentManager.java | 52 ++++- .../MlPlatformArchitecturesUtil.java | 127 +++++++++++ .../TransportPutTrainedModelActionTests.java | 70 ++++++ ...nedModelAssignmentClusterServiceTests.java | 178 ++++++++++++++- .../deployment/DeploymentManagerTests.java | 1 + .../MlPlatformArchitecturesUtilTests.java | 202 ++++++++++++++++++ .../xpack/ml/test/MockAppender.java | 36 ++++ .../rest-api-spec/test/ml/inference_crud.yml | 27 +++ 18 files changed, 947 insertions(+), 78 deletions(-) create mode 100644 docs/changelog/99584.yaml create mode 100644 x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/inference/deployment/MlPlatformArchitecturesUtil.java create mode 100644 x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/inference/deployment/MlPlatformArchitecturesUtilTests.java create mode 100644 x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/test/MockAppender.java diff --git a/docs/changelog/99584.yaml b/docs/changelog/99584.yaml new file mode 100644 index 0000000000000..229e3d8024506 --- /dev/null +++ b/docs/changelog/99584.yaml @@ -0,0 +1,5 @@ +pr: 99584 +summary: Adding an option for trained models to be platform specific +area: Machine Learning +type: enhancement +issues: [] diff --git a/docs/reference/ml/trained-models/apis/put-trained-models.asciidoc b/docs/reference/ml/trained-models/apis/put-trained-models.asciidoc index 82fd1872e6a76..7da46e13a8ce4 100644 --- a/docs/reference/ml/trained-models/apis/put-trained-models.asciidoc +++ b/docs/reference/ml/trained-models/apis/put-trained-models.asciidoc @@ -3,7 +3,9 @@ = Create trained models API [subs="attributes"] ++++ + Create trained models + ++++ Creates a trained model. @@ -1645,6 +1647,16 @@ Appropriate types are: * `pytorch`: The stored definition is a PyTorch (specifically a TorchScript) model. Currently only NLP models are supported. For more information, refer to {ml-docs}/ml-nlp.html[{nlp-cap}]. -- +`platform_architecture`:: +(Optional, string) +If the model only works on one platform, because it is heavily +optimized for a particular processor architecture and OS combination, +then this field specifies which. The format of the string must match +the platform identifiers used by Elasticsearch, so one of, `linux-x86_64`, +`linux-aarch64`, `darwin-x86_64`, `darwin-aarch64`, or `windows-x86_64`. +For portable models (those that work independent of processor architecture or +OS features), leave this field unset. + `tags`:: (Optional, string) diff --git a/server/src/main/java/org/elasticsearch/TransportVersions.java b/server/src/main/java/org/elasticsearch/TransportVersions.java index 360af92d6aa43..30cff2ce427be 100644 --- a/server/src/main/java/org/elasticsearch/TransportVersions.java +++ b/server/src/main/java/org/elasticsearch/TransportVersions.java @@ -147,6 +147,7 @@ static TransportVersion def(int id) { public static final TransportVersion NODE_INFO_COMPONENT_VERSIONS_ADDED = def(8_504_00_0); public static final TransportVersion COMPACT_FIELD_CAPS_ADDED = def(8_505_00_0); public static final TransportVersion DATA_STREAM_RESPONSE_INDEX_PROPERTIES = def(8_506_00_0); + public static final TransportVersion ML_TRAINED_MODEL_CONFIG_PLATFORM_ADDED = def(8_507_00_0); /* * STOP! READ THIS FIRST! No, really, diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/inference/TrainedModelConfig.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/inference/TrainedModelConfig.java index 9d4442f877b85..9dfa2d51f0fc0 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/inference/TrainedModelConfig.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/inference/TrainedModelConfig.java @@ -103,6 +103,7 @@ public class TrainedModelConfig implements ToXContentObject, Writeable { public static final ParseField PER_DEPLOYMENT_MEMORY_BYTES = new ParseField("per_deployment_memory_bytes"); public static final ParseField PER_ALLOCATION_MEMORY_BYTES = new ParseField("per_allocation_memory_bytes"); + public static final ParseField PLATFORM_ARCHITECTURE = new ParseField("platform_architecture"); public static final TransportVersion VERSION_3RD_PARTY_CONFIG_ADDED = TransportVersions.V_8_0_0; public static final TransportVersion VERSION_ALLOCATION_MEMORY_ADDED = TransportVersions.V_8_500_064; @@ -168,6 +169,7 @@ private static ObjectParser createParser(boole (p, c) -> ignoreUnknownFields ? ModelPackageConfig.fromXContentLenient(p) : ModelPackageConfig.fromXContentStrict(p), MODEL_PACKAGE ); + parser.declareString(TrainedModelConfig.Builder::setPlatformArchitecture, PLATFORM_ARCHITECTURE); return parser; } @@ -195,6 +197,7 @@ public static TrainedModelConfig.Builder fromXContent(XContentParser parser, boo private final TrainedModelLocation location; private final ModelPackageConfig modelPackageConfig; private Boolean fullDefinition; + private String platformArchitecture; TrainedModelConfig( String modelId, @@ -213,7 +216,8 @@ public static TrainedModelConfig.Builder fromXContent(XContentParser parser, boo Map defaultFieldMap, InferenceConfig inferenceConfig, TrainedModelLocation location, - ModelPackageConfig modelPackageConfig + ModelPackageConfig modelPackageConfig, + String platformArchitecture ) { this.modelId = ExceptionsHelper.requireNonNull(modelId, MODEL_ID); this.modelType = modelType; @@ -240,6 +244,7 @@ public static TrainedModelConfig.Builder fromXContent(XContentParser parser, boo this.inferenceConfig = inferenceConfig; this.location = location; this.modelPackageConfig = modelPackageConfig; + this.platformArchitecture = platformArchitecture; } private static TrainedModelInput handleDefaultInput(TrainedModelInput input, TrainedModelType modelType) { @@ -279,6 +284,11 @@ public TrainedModelConfig(StreamInput in) throws IOException { modelPackageConfig = null; fullDefinition = null; } + if (in.getTransportVersion().onOrAfter(TransportVersions.ML_TRAINED_MODEL_CONFIG_PLATFORM_ADDED)) { + platformArchitecture = in.readOptionalString(); + } else { + platformArchitecture = null; + } } public boolean isPackagedModel() { @@ -421,6 +431,10 @@ public long getPerAllocationMemoryBytes() { : 0L; } + public String getPlatformArchitecture() { + return platformArchitecture; + } + @Override public void writeTo(StreamOutput out) throws IOException { out.writeString(modelId); @@ -451,6 +465,10 @@ public void writeTo(StreamOutput out) throws IOException { out.writeOptionalWriteable(modelPackageConfig); out.writeOptionalBoolean(fullDefinition); } + + if (out.getTransportVersion().onOrAfter(TransportVersions.ML_TRAINED_MODEL_CONFIG_PLATFORM_ADDED)) { + out.writeOptionalString(platformArchitecture); + } } @Override @@ -463,6 +481,9 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws if (modelPackageConfig != null) { builder.field(MODEL_PACKAGE.getPreferredName(), modelPackageConfig); } + if (platformArchitecture != null) { + builder.field(PLATFORM_ARCHITECTURE.getPreferredName(), platformArchitecture); + } // If the model is to be exported for future import to another cluster, these fields are irrelevant. if (params.paramAsBoolean(EXCLUDE_GENERATED, false) == false) { @@ -543,7 +564,8 @@ public boolean equals(Object o) { && Objects.equals(defaultFieldMap, that.defaultFieldMap) && Objects.equals(inferenceConfig, that.inferenceConfig) && Objects.equals(metadata, that.metadata) - && Objects.equals(location, that.location); + && Objects.equals(location, that.location) + && Objects.equals(platformArchitecture, that.platformArchitecture); } @Override @@ -565,7 +587,8 @@ public int hashCode() { licenseLevel, inferenceConfig, defaultFieldMap, - location + location, + platformArchitecture ); } @@ -590,6 +613,7 @@ public static class Builder { private ModelPackageConfig modelPackageConfig; private Long perDeploymentMemoryBytes; private Long perAllocationMemoryBytes; + private String platformArchitecture; public Builder() {} @@ -611,6 +635,7 @@ public Builder(TrainedModelConfig config) { this.inferenceConfig = config.inferenceConfig; this.location = config.location; this.modelPackageConfig = config.modelPackageConfig; + this.platformArchitecture = config.platformArchitecture; } public Builder setModelId(String modelId) { @@ -703,6 +728,11 @@ public Builder setHyperparameters(List hyperparameters) { return addToMetadata(HYPERPARAMETERS, hyperparameters.stream().map(Hyperparameters::asMap).collect(Collectors.toList())); } + public Builder setPlatformArchitecture(String platformArchitecture) { + this.platformArchitecture = platformArchitecture; + return this; + } + public Builder setModelAliases(Set modelAliases) { if (modelAliases == null || modelAliases.isEmpty()) { return this; @@ -1022,7 +1052,8 @@ public TrainedModelConfig build() { defaultFieldMap, inferenceConfig, location, - modelPackageConfig + modelPackageConfig, + platformArchitecture ); } } diff --git a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/inference/persistence/InferenceIndexConstants.java b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/inference/persistence/InferenceIndexConstants.java index ca70f9e9e761d..6c8fc6fec4e0e 100644 --- a/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/inference/persistence/InferenceIndexConstants.java +++ b/x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/ml/inference/persistence/InferenceIndexConstants.java @@ -42,11 +42,15 @@ public final class InferenceIndexConstants { public static final ParseField DOC_TYPE = new ParseField("doc_type"); private static final String NATIVE_INDEX_PREFIX = INDEX_NAME_PREFIX + "native-"; - private static final String NATIVE_INDEX_VERSION = "000001"; + + // 000002 added support for platform specific models + private static final String NATIVE_INDEX_VERSION = "000002"; private static final String NATIVE_LATEST_INDEX = NATIVE_INDEX_PREFIX + NATIVE_INDEX_VERSION; private static final String MAPPINGS_VERSION_VARIABLE = "xpack.ml.version"; - public static final int INFERENCE_INDEX_MAPPINGS_VERSION = 1; + + // 2 added support for platform specific models + public static final int INFERENCE_INDEX_MAPPINGS_VERSION = 2; public static String mapping() { return TemplateUtils.loadTemplate( diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/inference/TrainedModelConfigTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/inference/TrainedModelConfigTests.java index 51f65fe0c9a0b..8b382beeb0644 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/inference/TrainedModelConfigTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/ml/inference/TrainedModelConfigTests.java @@ -7,6 +7,7 @@ package org.elasticsearch.xpack.core.ml.inference; import org.elasticsearch.TransportVersion; +import org.elasticsearch.TransportVersions; import org.elasticsearch.action.ActionRequestValidationException; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.io.stream.NamedWriteableRegistry; @@ -104,7 +105,8 @@ public static TrainedModelConfig.Builder createTestInstance(String modelId, bool .setLicenseLevel(randomFrom(License.OperationMode.PLATINUM.description(), License.OperationMode.BASIC.description())) .setInferenceConfig(randomFrom(inferenceConfigs)) .setTags(tags) - .setLocation(randomBoolean() ? null : IndexLocationTests.randomInstance()); + .setLocation(randomBoolean() ? null : IndexLocationTests.randomInstance()) + .setPlatformArchitecture(randomBoolean() ? null : randomAlphaOfLength(10)); } @Before @@ -191,7 +193,8 @@ public void testToXContentWithParams() throws IOException { .collect(Collectors.toMap(Function.identity(), (k) -> randomAlphaOfLength(10))), randomFrom(ClassificationConfigTests.randomClassificationConfig(), RegressionConfigTests.randomRegressionConfig()), null, - ModelPackageConfigTests.randomModulePackageConfig() + ModelPackageConfigTests.randomModulePackageConfig(), + randomAlphaOfLength(10) ); BytesReference reference = XContentHelper.toXContent(config, XContentType.JSON, ToXContent.EMPTY_PARAMS, false); @@ -241,7 +244,8 @@ public void testParseWithBothDefinitionAndCompressedSupplied() throws IOExceptio .collect(Collectors.toMap(Function.identity(), (k) -> randomAlphaOfLength(10))), randomFrom(ClassificationConfigTests.randomClassificationConfig(), RegressionConfigTests.randomRegressionConfig()), null, - ModelPackageConfigTests.randomModulePackageConfig() + ModelPackageConfigTests.randomModulePackageConfig(), + randomAlphaOfLength(10) ); BytesReference reference = XContentHelper.toXContent(config, XContentType.JSON, ToXContent.EMPTY_PARAMS, false); @@ -453,6 +457,9 @@ protected TrainedModelConfig mutateInstanceForVersion(TrainedModelConfig instanc if (instance.getInferenceConfig() instanceof NlpConfig nlpConfig) { builder.setInferenceConfig(InferenceConfigItemTestCase.mutateForVersion(nlpConfig, version)); } + if (version.before(TransportVersions.ML_TRAINED_MODEL_CONFIG_PLATFORM_ADDED)) { + builder.setPlatformArchitecture(null); + } return builder.build(); } } diff --git a/x-pack/plugin/core/template-resources/src/main/resources/ml/inference_index_mappings.json b/x-pack/plugin/core/template-resources/src/main/resources/ml/inference_index_mappings.json index 7ff961a0aac9c..77634546e0e6e 100644 --- a/x-pack/plugin/core/template-resources/src/main/resources/ml/inference_index_mappings.json +++ b/x-pack/plugin/core/template-resources/src/main/resources/ml/inference_index_mappings.json @@ -12,6 +12,9 @@ "model_id": { "type": "keyword" }, + "platform_architecture" : { + "type" : "keyword" + }, "created_by": { "type": "keyword" }, diff --git a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/MachineLearning.java b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/MachineLearning.java index c8e4fd488394a..d2d6bd4fcb443 100644 --- a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/MachineLearning.java +++ b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/MachineLearning.java @@ -1227,7 +1227,8 @@ public Collection createComponents( threadPool, new NodeLoadDetector(memoryTracker), systemAuditor, - nodeAvailabilityZoneMapper + nodeAvailabilityZoneMapper, + client ) ); diff --git a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportPutTrainedModelAction.java b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportPutTrainedModelAction.java index 6f97689222196..a0a2a81791550 100644 --- a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportPutTrainedModelAction.java +++ b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/action/TransportPutTrainedModelAction.java @@ -28,6 +28,7 @@ import org.elasticsearch.common.ValidationException; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.common.logging.HeaderWarning; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.unit.ByteSizeValue; import org.elasticsearch.common.util.concurrent.EsExecutors; @@ -71,6 +72,7 @@ import org.elasticsearch.xpack.core.ml.utils.ExceptionsHelper; import org.elasticsearch.xpack.ml.inference.ModelAliasMetadata; import org.elasticsearch.xpack.ml.inference.assignment.TrainedModelAssignmentMetadata; +import org.elasticsearch.xpack.ml.inference.deployment.MlPlatformArchitecturesUtil; import org.elasticsearch.xpack.ml.inference.persistence.TrainedModelProvider; import org.elasticsearch.xpack.ml.utils.TaskRetriever; @@ -78,6 +80,7 @@ import java.time.Instant; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.concurrent.atomic.AtomicReference; @@ -132,7 +135,7 @@ protected void masterOperation( Task task, PutTrainedModelAction.Request request, ClusterState state, - ActionListener listener + ActionListener finalResponseListener ) { TrainedModelConfig config = request.getTrainedModelConfig(); try { @@ -140,7 +143,9 @@ protected void masterOperation( config.ensureParsedDefinition(xContentRegistry); } } catch (IOException ex) { - listener.onFailure(ExceptionsHelper.badRequestException("Failed to parse definition for [{}]", ex, config.getModelId())); + finalResponseListener.onFailure( + ExceptionsHelper.badRequestException("Failed to parse definition for [{}]", ex, config.getModelId()) + ); return; } @@ -150,7 +155,7 @@ protected void masterOperation( try { config.getModelDefinition().getTrainedModel().validate(); } catch (ElasticsearchException ex) { - listener.onFailure( + finalResponseListener.onFailure( ExceptionsHelper.badRequestException("Definition for [{}] has validation failures.", ex, config.getModelId()) ); return; @@ -158,7 +163,7 @@ protected void masterOperation( TrainedModelType trainedModelType = TrainedModelType.typeFromTrainedModel(config.getModelDefinition().getTrainedModel()); if (trainedModelType == null) { - listener.onFailure( + finalResponseListener.onFailure( ExceptionsHelper.badRequestException( "Unknown trained model definition class [{}]", config.getModelDefinition().getTrainedModel().getName() @@ -171,7 +176,7 @@ protected void masterOperation( // Set the model type from the definition config = new TrainedModelConfig.Builder(config).setModelType(trainedModelType).build(); } else if (trainedModelType != config.getModelType()) { - listener.onFailure( + finalResponseListener.onFailure( ExceptionsHelper.badRequestException( "{} [{}] does not match the model definition type [{}]", TrainedModelConfig.MODEL_TYPE.getPreferredName(), @@ -183,7 +188,7 @@ protected void masterOperation( } if (config.getInferenceConfig().isTargetTypeSupported(config.getModelDefinition().getTrainedModel().targetType()) == false) { - listener.onFailure( + finalResponseListener.onFailure( ExceptionsHelper.badRequestException( "Model [{}] inference config type [{}] does not support definition target type [{}]", config.getModelId(), @@ -196,7 +201,7 @@ protected void masterOperation( TransportVersion minCompatibilityVersion = config.getModelDefinition().getTrainedModel().getMinimalCompatibilityVersion(); if (state.getMinTransportVersion().before(minCompatibilityVersion)) { - listener.onFailure( + finalResponseListener.onFailure( ExceptionsHelper.badRequestException( "Cannot create model [{}] while cluster upgrade is in progress.", config.getModelId() @@ -223,7 +228,7 @@ protected void masterOperation( } if (ModelAliasMetadata.fromState(state).getModelId(trainedModelConfig.getModelId()) != null) { - listener.onFailure( + finalResponseListener.onFailure( ExceptionsHelper.badRequestException( "requested model_id [{}] is the same as an existing model_alias. Model model_aliases and ids must be unique", config.getModelId() @@ -233,7 +238,7 @@ protected void masterOperation( } if (TrainedModelAssignmentMetadata.fromState(state).hasDeployment(trainedModelConfig.getModelId())) { - listener.onFailure( + finalResponseListener.onFailure( ExceptionsHelper.badRequestException( "Cannot create model [{}] the id is the same as an current model deployment", config.getModelId() @@ -242,6 +247,14 @@ protected void masterOperation( return; } + ActionListener finalResponseAction = ActionListener.wrap((configToReturn) -> { + finalResponseListener.onResponse(new PutTrainedModelAction.Response(configToReturn)); + }, finalResponseListener::onFailure); + + ActionListener verifyClusterAndModelArchitectures = ActionListener.wrap((configToReturn) -> { + verifyMlNodesAndModelArchitectures(configToReturn, client, threadPool, finalResponseAction); + }, finalResponseListener::onFailure); + ActionListener finishedStoringListener = ActionListener.wrap(bool -> { TrainedModelConfig configToReturn = trainedModelConfig.clearDefinition().build(); if (modelPackageConfigHolder.get() != null) { @@ -250,19 +263,19 @@ protected void masterOperation( modelPackageConfigHolder.get(), request.isWaitForCompletion(), ActionListener.wrap( - downloadTriggered -> listener.onResponse(new PutTrainedModelAction.Response(configToReturn)), - listener::onFailure + downloadTriggered -> verifyClusterAndModelArchitectures.onResponse(configToReturn), + finalResponseListener::onFailure ) ); } else { - listener.onResponse(new PutTrainedModelAction.Response(configToReturn)); + finalResponseListener.onResponse(new PutTrainedModelAction.Response(configToReturn)); } - }, listener::onFailure); + }, finalResponseListener::onFailure); var isPackageModel = config.isPackagedModel(); ActionListener checkStorageIndexSizeListener = ActionListener.wrap( r -> trainedModelProvider.storeTrainedModel(trainedModelConfig.build(), finishedStoringListener, isPackageModel), - listener::onFailure + finalResponseListener::onFailure ); ActionListener tagsModelIdCheckListener = ActionListener.wrap(r -> { @@ -276,7 +289,7 @@ protected void masterOperation( IndexStats indexStats = stats.getIndices().get(InferenceIndexConstants.nativeDefinitionStore()); if (indexStats != null && indexStats.getTotal().getStore().getSizeInBytes() > MAX_NATIVE_DEFINITION_INDEX_SIZE.getBytes()) { - listener.onFailure( + finalResponseListener.onFailure( new ElasticsearchStatusException( "Native model store has exceeded the maximum acceptable size of {}, " + "please delete older unused pytorch models", @@ -293,7 +306,7 @@ protected void masterOperation( checkStorageIndexSizeListener.onResponse(null); return; } - listener.onFailure( + finalResponseListener.onFailure( new ElasticsearchStatusException( "Unable to calculate stats for definition storage index [{}], please try again later", RestStatus.SERVICE_UNAVAILABLE, @@ -305,11 +318,11 @@ protected void masterOperation( return; } checkStorageIndexSizeListener.onResponse(null); - }, listener::onFailure); + }, finalResponseListener::onFailure); ActionListener modelIdTagCheckListener = ActionListener.wrap( r -> checkTagsAgainstModelIds(request.getTrainedModelConfig().getTags(), tagsModelIdCheckListener), - listener::onFailure + finalResponseListener::onFailure ); ActionListener handlePackageAndTagsListener = ActionListener.wrap(r -> { @@ -318,29 +331,61 @@ protected void masterOperation( try { TrainedModelValidator.validatePackage(trainedModelConfig, resolvedModelPackageConfig, state); } catch (ValidationException e) { - listener.onFailure(e); + finalResponseListener.onFailure(e); return; } modelPackageConfigHolder.set(resolvedModelPackageConfig); setTrainedModelConfigFieldsFromPackagedModel(trainedModelConfig, resolvedModelPackageConfig, xContentRegistry); checkModelIdAgainstTags(trainedModelConfig.getModelId(), modelIdTagCheckListener); - }, listener::onFailure)); + }, finalResponseListener::onFailure)); } else { checkModelIdAgainstTags(trainedModelConfig.getModelId(), modelIdTagCheckListener); } - }, listener::onFailure); + }, finalResponseListener::onFailure); checkForExistingTask( client, trainedModelConfig.getModelId(), request.isWaitForCompletion(), - listener, + finalResponseListener, handlePackageAndTagsListener, request.timeout() ); } + void verifyMlNodesAndModelArchitectures( + TrainedModelConfig configToReturn, + Client client, + ThreadPool threadPool, + ActionListener configToReturnListener + ) { + ActionListener addWarningHeaderOnFailureListener = new ActionListener() { + @Override + public void onResponse(TrainedModelConfig config) { + assert Objects.equals(config, configToReturn); + configToReturnListener.onResponse(configToReturn); + } + + @Override + public void onFailure(Exception e) { + HeaderWarning.addWarning(e.getMessage()); + configToReturnListener.onResponse(configToReturn); + } + }; + + callVerifyMlNodesAndModelArchitectures(configToReturn, addWarningHeaderOnFailureListener, client, threadPool); + } + + void callVerifyMlNodesAndModelArchitectures( + TrainedModelConfig configToReturn, + ActionListener failureListener, + Client client, + ThreadPool threadPool + ) { + MlPlatformArchitecturesUtil.verifyMlNodesAndModelArchitectures(failureListener, client, threadPool, configToReturn); + } + /** * This method is package private for testing */ diff --git a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/inference/assignment/TrainedModelAssignmentClusterService.java b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/inference/assignment/TrainedModelAssignmentClusterService.java index efc8bd84c6350..ea52c4918d05b 100644 --- a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/inference/assignment/TrainedModelAssignmentClusterService.java +++ b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/inference/assignment/TrainedModelAssignmentClusterService.java @@ -15,6 +15,7 @@ import org.elasticsearch.TransportVersions; import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.support.master.AcknowledgedResponse; +import org.elasticsearch.client.internal.Client; import org.elasticsearch.cluster.ClusterChangedEvent; import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.ClusterStateListener; @@ -47,6 +48,7 @@ import org.elasticsearch.xpack.ml.MachineLearning; import org.elasticsearch.xpack.ml.autoscaling.NodeAvailabilityZoneMapper; import org.elasticsearch.xpack.ml.inference.assignment.planning.AllocationReducer; +import org.elasticsearch.xpack.ml.inference.deployment.MlPlatformArchitecturesUtil; import org.elasticsearch.xpack.ml.job.NodeLoad; import org.elasticsearch.xpack.ml.job.NodeLoadDetector; import org.elasticsearch.xpack.ml.notifications.SystemAuditor; @@ -78,6 +80,7 @@ public class TrainedModelAssignmentClusterService implements ClusterStateListene private final NodeLoadDetector nodeLoadDetector; private final SystemAuditor systemAuditor; private final NodeAvailabilityZoneMapper nodeAvailabilityZoneMapper; + private final Client client; private volatile int maxMemoryPercentage; private volatile boolean useAuto; private volatile int maxOpenJobs; @@ -91,7 +94,8 @@ public TrainedModelAssignmentClusterService( ThreadPool threadPool, NodeLoadDetector nodeLoadDetector, SystemAuditor systemAuditor, - NodeAvailabilityZoneMapper nodeAvailabilityZoneMapper + NodeAvailabilityZoneMapper nodeAvailabilityZoneMapper, + Client client ) { this.clusterService = Objects.requireNonNull(clusterService); this.threadPool = Objects.requireNonNull(threadPool); @@ -104,6 +108,7 @@ public TrainedModelAssignmentClusterService( this.maxLazyMLNodes = MachineLearning.MAX_LAZY_ML_NODES.get(settings); this.maxMLNodeSize = MachineLearning.MAX_ML_NODE_SIZE.get(settings).getBytes(); this.allocatedProcessorsScale = MachineLearning.ALLOCATED_PROCESSORS_SCALE.get(settings); + this.client = client; // Only nodes that can possibly be master nodes really need this service running if (DiscoveryNode.isMasterNode(settings)) { clusterService.addListener(this); @@ -150,14 +155,14 @@ private void submitUnbatchedTask(@SuppressWarnings("SameParameterValue") String @Override public void clusterChanged(ClusterChangedEvent event) { - if (event.state().blocks().hasGlobalBlock(GatewayService.STATE_NOT_RECOVERED_BLOCK)) { + if (eventStateHasGlobalBlockStateNotRecoveredBlock(event)) { return; } if (event.localNodeMaster() == false) { return; } - if (event.state().getMinTransportVersion().before(DISTRIBUTED_MODEL_ALLOCATION_TRANSPORT_VERSION)) { + if (eventStateMinTransportVersionIsBeforeDistributedModelAllocationTransportVersion(event)) { // we should not try to rebalance assignments while there may be nodes running on a version // prior to introducing distributed model allocation. // But we should remove routing to removed or shutting down nodes. @@ -165,6 +170,10 @@ public void clusterChanged(ClusterChangedEvent event) { return; } + if (event.nodesAdded()) { + logMlNodeHeterogeneity(); + } + Optional rebalanceReason = detectReasonToRebalanceModels(event); if (rebalanceReason.isPresent()) { // As this produces a cluster state update task, we are certain that if the persistent @@ -187,6 +196,42 @@ public void clusterChanged(ClusterChangedEvent event) { } } + boolean eventStateMinTransportVersionIsBeforeDistributedModelAllocationTransportVersion(ClusterChangedEvent event) { + return event.state().getMinTransportVersion().before(DISTRIBUTED_MODEL_ALLOCATION_TRANSPORT_VERSION); + } + + boolean eventStateHasGlobalBlockStateNotRecoveredBlock(ClusterChangedEvent event) { + return event.state().blocks().hasGlobalBlock(GatewayService.STATE_NOT_RECOVERED_BLOCK); + } + + void logMlNodeHeterogeneity() { + ActionListener> architecturesListener = getArchitecturesSetActionListener(); + MlPlatformArchitecturesUtil.getMlNodesArchitecturesSet(architecturesListener, client, threadPool); + } + + static ActionListener> getArchitecturesSetActionListener() { + ActionListener> architecturesListener = new ActionListener>() { + @Override + public void onResponse(Set architectures) { + if (architectures.size() > 1) { + logger.warn( + format( + "Heterogeneous platform architectures were detected among ML nodes. " + + "This will prevent the deployment of some trained models. Distinct platform architectures detected: %s", + architectures + ) + ); + } + } + + @Override + public void onFailure(Exception e) { + logger.error("Failed to detect heterogeneity among ML nodes with exception: ", e); + } + }; + return architecturesListener; + } + private void removeRoutingToRemovedOrShuttingDownNodes(ClusterChangedEvent event) { if (areAssignedNodesRemoved(event)) { submitUnbatchedTask("removing routing entries for removed or shutting down nodes", new ClusterStateUpdateTask() { @@ -486,51 +531,89 @@ private void rebalanceAssignments( String reason, ActionListener listener ) { - threadPool.executor(MachineLearning.UTILITY_THREAD_POOL_NAME).execute(() -> { - logger.debug(() -> format("Rebalancing model allocations because [%s]", reason)); - TrainedModelAssignmentMetadata.Builder rebalancedMetadata; - try { - rebalancedMetadata = rebalanceAssignments(clusterState, modelToAdd); - } catch (Exception e) { - listener.onFailure(e); - return; - } + ActionListener> architecturesListener = ActionListener.wrap((mlNodesArchitectures) -> { + threadPool.executor(MachineLearning.UTILITY_THREAD_POOL_NAME).execute(() -> { + logger.debug(() -> format("Rebalancing model allocations because [%s]", reason)); + + TrainedModelAssignmentMetadata.Builder rebalancedMetadata; + try { + rebalancedMetadata = rebalanceAssignments(clusterState, modelToAdd); + } catch (Exception e) { + listener.onFailure(e); + return; + } - submitUnbatchedTask(reason, new ClusterStateUpdateTask() { + submitUnbatchedTask(reason, new ClusterStateUpdateTask() { - private volatile boolean isUpdated; - private volatile boolean isChanged; + private volatile boolean isUpdated; + private volatile boolean isChanged; - @Override - public ClusterState execute(ClusterState currentState) { + @Override + public ClusterState execute(ClusterState currentState) { - if (areClusterStatesCompatibleForRebalance(clusterState, currentState)) { - isUpdated = true; - ClusterState updatedState = update(currentState, rebalancedMetadata); - isChanged = updatedState != currentState; - return updatedState; + currentState = stopPlatformSpecificModelsInHeterogeneousClusters( + currentState, + mlNodesArchitectures, + modelToAdd, + clusterState + ); + + if (areClusterStatesCompatibleForRebalance(clusterState, currentState)) { + isUpdated = true; + ClusterState updatedState = update(currentState, rebalancedMetadata); + isChanged = updatedState != currentState; + return updatedState; + } + + rebalanceAssignments(currentState, modelToAdd, reason, listener); + return currentState; } - rebalanceAssignments(currentState, modelToAdd, reason, listener); - return currentState; - } - @Override - public void onFailure(Exception e) { - listener.onFailure(e); - } + @Override + public void onFailure(Exception e) { + listener.onFailure(e); + } - @Override - public void clusterStateProcessed(ClusterState oldState, ClusterState newState) { - if (isUpdated) { - if (isChanged) { - threadPool.executor(MachineLearning.UTILITY_THREAD_POOL_NAME) - .execute(() -> systemAuditor.info(Messages.getMessage(Messages.INFERENCE_DEPLOYMENT_REBALANCED, reason))); + @Override + public void clusterStateProcessed(ClusterState oldState, ClusterState newState) { + if (isUpdated) { + if (isChanged) { + threadPool.executor(MachineLearning.UTILITY_THREAD_POOL_NAME) + .execute( + () -> systemAuditor.info(Messages.getMessage(Messages.INFERENCE_DEPLOYMENT_REBALANCED, reason)) + ); + } + listener.onResponse(TrainedModelAssignmentMetadata.fromState(newState)); } - listener.onResponse(TrainedModelAssignmentMetadata.fromState(newState)); } - } + }); }); - }); + }, listener::onFailure); + + MlPlatformArchitecturesUtil.getMlNodesArchitecturesSet(architecturesListener, client, threadPool); + } + + ClusterState stopPlatformSpecificModelsInHeterogeneousClusters( + ClusterState updatedState, + Set mlNodesArchitectures, + Optional modelToAdd, + ClusterState clusterState + ) { + if (mlNodesArchitectures.size() > 1 && modelToAdd.isPresent()) { + String reasonToStop = format( + "ML nodes in this cluster have multiple platform architectures, " + + "but can only have one for this model ([%s]); " + + "detected architectures: %s", + modelToAdd.get().getModelId(), + mlNodesArchitectures + ); + updatedState = callSetToStopping(reasonToStop, modelToAdd.get().getDeploymentId(), clusterState); + } + return updatedState; + } + + ClusterState callSetToStopping(String reasonToStop, String deploymentId, ClusterState clusterState) { + return setToStopping(clusterState, deploymentId, reasonToStop); } private boolean areClusterStatesCompatibleForRebalance(ClusterState source, ClusterState target) { diff --git a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/inference/deployment/DeploymentManager.java b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/inference/deployment/DeploymentManager.java index 03f34dacb1faf..fcb44d0f391fe 100644 --- a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/inference/deployment/DeploymentManager.java +++ b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/inference/deployment/DeploymentManager.java @@ -164,9 +164,7 @@ public void startDeployment(TrainedModelDeploymentTask task, ActionListener getModelListener = ActionListener.wrap(getModelResponse -> { - assert getModelResponse.getResources().results().size() == 1; - TrainedModelConfig modelConfig = getModelResponse.getResources().results().get(0); + ActionListener getVerifiedModel = ActionListener.wrap((modelConfig) -> { processContext.modelInput.set(modelConfig.getInput()); if (modelConfig.getInferenceConfig() instanceof NlpConfig nlpConfig) { @@ -209,15 +207,57 @@ public void startDeployment(TrainedModelDeploymentTask task, ActionListener verifyModelAndClusterArchitecturesListener = ActionListener.wrap( + getModelResponse -> { + assert getModelResponse.getResources().results().size() == 1; + TrainedModelConfig modelConfig = getModelResponse.getResources().results().get(0); + + verifyMlNodesAndModelArchitectures(modelConfig, client, threadPool, getVerifiedModel); + + }, + failedDeploymentListener::onFailure + ); + executeAsyncWithOrigin( client, ML_ORIGIN, GetTrainedModelsAction.INSTANCE, new GetTrainedModelsAction.Request(task.getParams().getModelId()), - getModelListener + verifyModelAndClusterArchitecturesListener ); } + void verifyMlNodesAndModelArchitectures( + TrainedModelConfig configToReturn, + Client client, + ThreadPool threadPool, + ActionListener configToReturnListener + ) { + ActionListener verifyConfigListener = new ActionListener() { + @Override + public void onResponse(TrainedModelConfig config) { + assert Objects.equals(config, configToReturn); + configToReturnListener.onResponse(configToReturn); + } + + @Override + public void onFailure(Exception e) { + configToReturnListener.onFailure(e); + } + }; + + callVerifyMlNodesAndModelArchitectures(configToReturn, verifyConfigListener, client, threadPool); + } + + void callVerifyMlNodesAndModelArchitectures( + TrainedModelConfig configToReturn, + ActionListener configToReturnListener, + Client client, + ThreadPool threadPool + ) { + MlPlatformArchitecturesUtil.verifyMlNodesAndModelArchitectures(configToReturnListener, client, threadPool, configToReturn); + } + private SearchRequest vocabSearchRequest(VocabularyConfig vocabularyConfig, String modelId) { return client.prepareSearch(vocabularyConfig.getIndex()) .setQuery(new IdsQueryBuilder().addIds(VocabularyConfig.docId(modelId))) @@ -394,11 +434,11 @@ class ProcessContext { private final PyTorchResultProcessor resultProcessor; private final PyTorchStateStreamer stateStreamer; private final PriorityProcessWorkerExecutorService priorityProcessWorker; + private final AtomicInteger rejectedExecutionCount = new AtomicInteger(); + private final AtomicInteger timeoutCount = new AtomicInteger(); private volatile Instant startTime; private volatile Integer numThreadsPerAllocation; private volatile Integer numAllocations; - private final AtomicInteger rejectedExecutionCount = new AtomicInteger(); - private final AtomicInteger timeoutCount = new AtomicInteger(); private volatile boolean isStopped; private static final TimeValue COMPLETION_TIMEOUT = TimeValue.timeValueMinutes(3); diff --git a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/inference/deployment/MlPlatformArchitecturesUtil.java b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/inference/deployment/MlPlatformArchitecturesUtil.java new file mode 100644 index 0000000000000..ff8ac1dbb3eec --- /dev/null +++ b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/inference/deployment/MlPlatformArchitecturesUtil.java @@ -0,0 +1,127 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.ml.inference.deployment; + +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.admin.cluster.node.info.NodesInfoAction; +import org.elasticsearch.action.admin.cluster.node.info.NodesInfoRequest; +import org.elasticsearch.action.admin.cluster.node.info.NodesInfoRequestBuilder; +import org.elasticsearch.action.admin.cluster.node.info.NodesInfoResponse; +import org.elasticsearch.client.internal.Client; +import org.elasticsearch.cluster.node.DiscoveryNodeRole; +import org.elasticsearch.monitor.os.OsInfo; +import org.elasticsearch.plugins.Platforms; +import org.elasticsearch.threadpool.ThreadPool; +import org.elasticsearch.xpack.core.ml.inference.TrainedModelConfig; +import org.elasticsearch.xpack.ml.MachineLearning; + +import java.util.Iterator; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +import static org.elasticsearch.core.Strings.format; +import static org.elasticsearch.xpack.core.ClientHelper.ML_ORIGIN; +import static org.elasticsearch.xpack.core.ClientHelper.executeAsyncWithOrigin; + +public class MlPlatformArchitecturesUtil { + + public static void getMlNodesArchitecturesSet(ActionListener> architecturesListener, Client client, ThreadPool threadPool) { + ActionListener listener = MlPlatformArchitecturesUtil.getArchitecturesSetFromNodesInfoResponseListener( + threadPool, + architecturesListener + ); + + NodesInfoRequest request = MlPlatformArchitecturesUtil.getNodesInfoBuilderWithMlNodeArchitectureInfo(client).request(); + executeAsyncWithOrigin(client, ML_ORIGIN, NodesInfoAction.INSTANCE, request, listener); + } + + static ActionListener getArchitecturesSetFromNodesInfoResponseListener( + ThreadPool threadPool, + ActionListener> architecturesListener + ) { + return ActionListener.wrap(nodesInfoResponse -> { + threadPool.executor(MachineLearning.UTILITY_THREAD_POOL_NAME).execute(() -> { + architecturesListener.onResponse(getArchitecturesSetFromNodesInfoResponse(nodesInfoResponse)); + }); + }, architecturesListener::onFailure); + } + + static NodesInfoRequestBuilder getNodesInfoBuilderWithMlNodeArchitectureInfo(Client client) { + return client.admin().cluster().prepareNodesInfo().clear().setNodesIds("ml:true").setOs(true).setPlugins(true); + } + + private static Set getArchitecturesSetFromNodesInfoResponse(NodesInfoResponse nodesInfoResponse) { + return nodesInfoResponse.getNodes() + .stream() + .filter(node -> node.getNode().hasRole(DiscoveryNodeRole.ML_ROLE.roleName())) + .map(node -> { + OsInfo osInfo = node.getInfo(OsInfo.class); + return Platforms.platformName(osInfo.getName(), osInfo.getArch()); + }) + .collect(Collectors.toUnmodifiableSet()); + } + + public static void verifyMlNodesAndModelArchitectures( + ActionListener successOrFailureListener, + Client client, + ThreadPool threadPool, + TrainedModelConfig configToReturn + ) { + String modelID = configToReturn.getModelId(); + String modelPlatformArchitecture = configToReturn.getPlatformArchitecture(); + + String modifiedPlatformArchitecture = (modelPlatformArchitecture == null && modelID.contains("linux-x86_64")) + ? "linux-x86_64" + : null; + ActionListener> architecturesListener = ActionListener.wrap((architectures) -> { + verifyMlNodesAndModelArchitectures(architectures, modifiedPlatformArchitecture, modelID); + successOrFailureListener.onResponse(configToReturn); + }, successOrFailureListener::onFailure); + + getMlNodesArchitecturesSet(architecturesListener, client, threadPool); + } + + static void verifyMlNodesAndModelArchitectures(Set architectures, String modelPlatformArchitecture, String modelID) + throws IllegalArgumentException, IllegalStateException { + + String architecture = null; + Iterator architecturesIterator = architectures.iterator(); + // If there are no ML nodes at all in the current cluster we assume that any that are added later will work + if (modelPlatformArchitecture == null || architectures.isEmpty() || architecturesIterator.hasNext() == false) { + return; + } + + if (architectures.size() > 1) { + throw new IllegalStateException( + format( + "ML nodes in this cluster have multiple platform architectures, but can only have one for this model ([%s]); " + + "expected [%s]; " + + "but was %s", + modelID, + modelPlatformArchitecture, + architectures + ) + ); + } + + if (Objects.equals(architecturesIterator.next(), modelPlatformArchitecture) == false) { + + throw new IllegalArgumentException( + format( + "The model being deployed ([%s]) is platform specific and incompatible with ML nodes in the cluster; " + + "expected [%s]; " + + "but was %s", + modelID, + modelPlatformArchitecture, + architectures + ) + ); + } + } +} diff --git a/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/action/TransportPutTrainedModelActionTests.java b/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/action/TransportPutTrainedModelActionTests.java index 514a1e2243531..f708ef1fb2959 100644 --- a/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/action/TransportPutTrainedModelActionTests.java +++ b/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/action/TransportPutTrainedModelActionTests.java @@ -11,9 +11,12 @@ import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.admin.cluster.node.tasks.list.ListTasksAction; import org.elasticsearch.action.admin.cluster.node.tasks.list.ListTasksResponse; +import org.elasticsearch.action.support.ActionFilters; import org.elasticsearch.action.support.PlainActionFuture; import org.elasticsearch.client.internal.Client; +import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.bytes.BytesReference; +import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.LoggingDeprecationHandler; import org.elasticsearch.common.xcontent.XContentHelper; import org.elasticsearch.core.TimeValue; @@ -21,6 +24,7 @@ import org.elasticsearch.test.ESTestCase; import org.elasticsearch.threadpool.TestThreadPool; import org.elasticsearch.threadpool.ThreadPool; +import org.elasticsearch.transport.TransportService; import org.elasticsearch.xcontent.NamedXContentRegistry; import org.elasticsearch.xcontent.ToXContent; import org.elasticsearch.xcontent.XContentBuilder; @@ -50,6 +54,7 @@ import org.elasticsearch.xpack.core.ml.inference.trainedmodel.TextSimilarityConfigTests; import org.junit.After; import org.junit.Before; +import org.mockito.ArgumentCaptor; import java.io.IOException; import java.util.Collections; @@ -65,6 +70,11 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.same; import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; public class TransportPutTrainedModelActionTests extends ESTestCase { private static final TimeValue TIMEOUT = new TimeValue(30, TimeUnit.SECONDS); @@ -205,6 +215,42 @@ public void testCheckForExistingTaskReturnsTask() { assertThat(returnedModel.getResponse().getModelId(), is(trainedModel.getModelId())); } + public void testVerifyMlNodesAndModelArchitectures_GivenIllegalArgumentException_ThenSetHeaderWarning() { + + TransportPutTrainedModelAction actionSpy = spy(createTransportPutTrainedModelAction()); + @SuppressWarnings("unchecked") + ArgumentCaptor> failureListener = ArgumentCaptor.forClass(ActionListener.class); + @SuppressWarnings("unchecked") + ActionListener mockConfigToReturnListener = mock(ActionListener.class); + TrainedModelConfig mockConfigToReturn = mock(TrainedModelConfig.class); + doNothing().when(mockConfigToReturnListener).onResponse(any()); + + doNothing().when(actionSpy).callVerifyMlNodesAndModelArchitectures(any(), any(), any(), any()); + actionSpy.verifyMlNodesAndModelArchitectures(mockConfigToReturn, null, threadPool, mockConfigToReturnListener); + verify(actionSpy).verifyMlNodesAndModelArchitectures(any(), any(), any(), any()); + verify(actionSpy).callVerifyMlNodesAndModelArchitectures(any(), failureListener.capture(), any(), any()); + + String warningMessage = "TEST HEADER WARNING"; + failureListener.getValue().onFailure(new IllegalArgumentException(warningMessage)); + assertWarnings(warningMessage); + } + + public void testVerifyMlNodesAndModelArchitectures_GivenArchitecturesMatch_ThenTriggerOnResponse() { + + TransportPutTrainedModelAction actionSpy = spy(createTransportPutTrainedModelAction()); + @SuppressWarnings("unchecked") + ArgumentCaptor> successListener = ArgumentCaptor.forClass(ActionListener.class); + @SuppressWarnings("unchecked") + ActionListener mockConfigToReturnListener = mock(ActionListener.class); + TrainedModelConfig mockConfigToReturn = mock(TrainedModelConfig.class); + + doNothing().when(actionSpy).callVerifyMlNodesAndModelArchitectures(any(), any(), any(), any()); + actionSpy.verifyMlNodesAndModelArchitectures(mockConfigToReturn, null, threadPool, mockConfigToReturnListener); + verify(actionSpy).callVerifyMlNodesAndModelArchitectures(any(), successListener.capture(), any(), any()); + + ensureNoWarnings(); + } + private static void prepareGetTrainedModelResponse(Client client, List trainedModels) { doAnswer(invocationOnMock -> { @SuppressWarnings("unchecked") @@ -220,6 +266,30 @@ private static void prepareGetTrainedModelResponse(Client client, List architecturesSet = new HashSet<>(randomList(0, 1, () -> randomAlphaOfLength(10))); + + final ActionListener> underTestListener = TrainedModelAssignmentClusterService.getArchitecturesSetActionListener(); + + underTestListener.onResponse(architecturesSet); + + LogEvent lastEvent = appender.getLastEventAndReset(); + assertNull(lastEvent); + } + + public void testLogMlNodeHeterogeneity_GivenTwoArchitecture_ThenWarn() throws InterruptedException { + String nodeArch = randomAlphaOfLength(10); + Set architecturesSet = Set.of(nodeArch, nodeArch + "2"); // architectures must be different + + final ActionListener> underTestListener = TrainedModelAssignmentClusterService.getArchitecturesSetActionListener(); + underTestListener.onResponse(architecturesSet); + + LogEvent lastEvent = appender.getLastEventAndReset(); + + assertEquals(Level.WARN, lastEvent.getLevel()); + + Message m = lastEvent.getMessage(); + String fm = m.getFormattedMessage(); + String expected = Strings.format( + "Heterogeneous platform architectures were detected among ML nodes. " + + "This will prevent the deployment of some trained models. Distinct platform architectures detected: %s", + architecturesSet + ); + + assertEquals(expected, fm); + } + + public void testLogMlNodeHeterogeneity_GivenFailure_ThenError() throws InterruptedException { + RuntimeException e = new RuntimeException("Test Runtime Exception"); + final ActionListener> underTestListener = TrainedModelAssignmentClusterService.getArchitecturesSetActionListener(); + underTestListener.onFailure(e); + + LogEvent lastEvent = appender.getLastEventAndReset(); + + assertEquals(Level.ERROR, lastEvent.getLevel()); + + Message m = lastEvent.getMessage(); + String fm = m.getFormattedMessage(); + + assertEquals("Failed to detect heterogeneity among ML nodes with exception: ", fm); + assertEquals(e, lastEvent.getThrown()); + } + + public void testClusterChanged_GivenNodesAdded_ThenLogMlNodeHeterogeneityCalled() { + nodeAvailabilityZoneMapper = mock(NodeAvailabilityZoneMapper.class); + TrainedModelAssignmentClusterService serviceSpy = spy(createClusterService(randomInt(5))); + doNothing().when(serviceSpy).logMlNodeHeterogeneity(); + doReturn(false).when(serviceSpy).eventStateHasGlobalBlockStateNotRecoveredBlock(any()); + doReturn(false).when(serviceSpy).eventStateMinTransportVersionIsBeforeDistributedModelAllocationTransportVersion(any()); + + ClusterChangedEvent mockNodesAddedEvent = mock(ClusterChangedEvent.class); + ClusterState mockState = mock(ClusterState.class); + doReturn(mockState).when(mockNodesAddedEvent).state(); + Metadata mockMetadata = mock(Metadata.class); + doReturn(mockMetadata).when(mockState).getMetadata(); + doReturn(null).when(mockState).custom(anyString()); + + doReturn(true).when(mockNodesAddedEvent).localNodeMaster(); + doReturn(true).when(mockNodesAddedEvent).nodesAdded(); + + serviceSpy.clusterChanged(mockNodesAddedEvent); + Mockito.verify(serviceSpy).logMlNodeHeterogeneity(); + Mockito.verify(mockNodesAddedEvent).nodesAdded(); + } + + public void testStopPlatformSpecificModelsInHeterogeneousClusters_GivenMultipleMlNodeArchitectures_ThenCallSetToStopping() { + nodeAvailabilityZoneMapper = mock(NodeAvailabilityZoneMapper.class); + TrainedModelAssignmentClusterService serviceSpy = spy(createClusterService(randomInt(5))); + + Set architecturesSet = new HashSet<>(randomList(2, 5, () -> randomAlphaOfLength(10))); + ClusterState mockUpdatedState = mock(ClusterState.class); + ClusterState mockClusterState = mock(ClusterState.class); + StartTrainedModelDeploymentAction.TaskParams mockModelToAdd = mock(StartTrainedModelDeploymentAction.TaskParams.class); + Optional optionalModelToAdd = Optional.of(mockModelToAdd); + String modelId = randomAlphaOfLength(10); + String deploymentId = randomAlphaOfLength(10); + when(mockModelToAdd.getModelId()).thenReturn(modelId); + when(mockModelToAdd.getDeploymentId()).thenReturn(deploymentId); + + String reasonToStop = format( + "ML nodes in this cluster have multiple platform architectures, " + + "but can only have one for this model ([%s]); " + + "detected architectures: %s", + modelId, + architecturesSet + ); + + doReturn(mockUpdatedState).when(serviceSpy).callSetToStopping(reasonToStop, deploymentId, mockClusterState); + + ClusterState updatedMockClusterState = serviceSpy.stopPlatformSpecificModelsInHeterogeneousClusters( + mockUpdatedState, + architecturesSet, + optionalModelToAdd, + mockClusterState + ); + + verify(serviceSpy).callSetToStopping(reasonToStop, deploymentId, mockClusterState); } public void testUpdateModelRoutingTable() { @@ -1878,7 +2019,8 @@ private TrainedModelAssignmentClusterService createClusterService(int maxLazyNod threadPool, nodeLoadDetector, systemAuditor, - nodeAvailabilityZoneMapper + nodeAvailabilityZoneMapper, + client ); } @@ -1948,4 +2090,36 @@ private static StartTrainedModelDeploymentAction.TaskParams newParams( ); } + protected void assertAsync( + Consumer> function, + T expected, + CheckedConsumer onAnswer, + Consumer onException + ) throws InterruptedException { + + CountDownLatch latch = new CountDownLatch(1); + + LatchedActionListener listener = new LatchedActionListener<>(ActionListener.wrap(r -> { + if (expected == null) { + fail("expected an exception but got a response"); + } else { + assertThat(r, equalTo(expected)); + } + if (onAnswer != null) { + onAnswer.accept(r); + } + }, e -> { + if (onException == null) { + logger.error("got unexpected exception", e); + fail("got unexpected exception: " + e.getMessage()); + } else { + onException.accept(e); + } + }), latch); + + function.accept(listener); + latch.countDown(); + assertTrue("timed out after 20s", latch.await(20, TimeUnit.SECONDS)); + } + } diff --git a/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/inference/deployment/DeploymentManagerTests.java b/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/inference/deployment/DeploymentManagerTests.java index 0bc898f434030..028c4b48ad355 100644 --- a/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/inference/deployment/DeploymentManagerTests.java +++ b/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/inference/deployment/DeploymentManagerTests.java @@ -108,4 +108,5 @@ public void testRejectedExecution() { assertThat(rejectedCount.intValue(), equalTo(1)); } + } diff --git a/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/inference/deployment/MlPlatformArchitecturesUtilTests.java b/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/inference/deployment/MlPlatformArchitecturesUtilTests.java new file mode 100644 index 0000000000000..28fc3db10cbe8 --- /dev/null +++ b/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/inference/deployment/MlPlatformArchitecturesUtilTests.java @@ -0,0 +1,202 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.ml.inference.deployment; + +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.LatchedActionListener; +import org.elasticsearch.action.admin.cluster.node.info.NodeInfo; +import org.elasticsearch.action.admin.cluster.node.info.NodesInfoResponse; +import org.elasticsearch.cluster.node.DiscoveryNode; +import org.elasticsearch.cluster.node.DiscoveryNodeRole; +import org.elasticsearch.core.CheckedConsumer; +import org.elasticsearch.monitor.os.OsInfo; +import org.elasticsearch.plugins.Platforms; +import org.elasticsearch.test.ESTestCase; +import org.elasticsearch.threadpool.ThreadPool; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; +import java.util.stream.Collectors; + +import static org.hamcrest.Matchers.equalTo; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class MlPlatformArchitecturesUtilTests extends ESTestCase { + + public void testGetNodesOsArchitectures() throws InterruptedException { + var threadPool = mock(ThreadPool.class); + var mockExectutorServervice = mock(ExecutorService.class); + doNothing().when(mockExectutorServervice).execute(any()); + when(threadPool.executor(anyString())).thenReturn(mockExectutorServervice); + + var mockNodesInfoResponse = mock(NodesInfoResponse.class); + List nodeInfoList = randomNodeInfos(4); + when(mockNodesInfoResponse.getNodes()).thenReturn(nodeInfoList); + + var expected = nodeInfoList.stream().filter(node -> node.getNode().hasRole(DiscoveryNodeRole.ML_ROLE.roleName())).map(node -> { + OsInfo osInfo = node.getInfo(OsInfo.class); + return Platforms.platformName(osInfo.getName(), osInfo.getArch()); + }).collect(Collectors.toUnmodifiableSet()); + + assertAsync(new Consumer>>() { + @Override + public void accept(ActionListener> setActionListener) { + final ActionListener nodesInfoResponseActionListener = MlPlatformArchitecturesUtil + .getArchitecturesSetFromNodesInfoResponseListener(threadPool, setActionListener); + nodesInfoResponseActionListener.onResponse(mockNodesInfoResponse); + } + + }, expected, null, null); + } + + public void testVerifyMlNodesAndModelArchitectures_GivenNullModelArchitecture_ThenNothing() { + var architectures = nArchitectures(randomIntBetween(2, 10)); + MlPlatformArchitecturesUtil.verifyMlNodesAndModelArchitectures(architectures, null, randomAlphaOfLength(10)); + } + + public void testVerifyMlNodesAndModelArchitectures_GivenZeroArches_ThenNothing() { + var architectures = new HashSet(); + MlPlatformArchitecturesUtil.verifyMlNodesAndModelArchitectures(architectures, randomAlphaOfLength(10), randomAlphaOfLength(10)); + } + + public void testVerifyMlNodesAndModelArchitectures_GivenOneArchMatches_ThenNothing() { + Set architectures = nArchitectures(1); + String architecture = architectures.iterator().next(); + MlPlatformArchitecturesUtil.verifyMlNodesAndModelArchitectures(architectures, architecture, randomAlphaOfLength(10)); + } + + public void testVerifyMlNodesAndModelArchitectures_GivenAtLeastTwoArches_ThenThrowsISE() { + var architectures = nArchitectures(randomIntBetween(2, 10)); + var modelId = randomAlphaOfLength(10); + var requiredArch = randomAlphaOfLength(10); + String message = "ML nodes in this cluster have multiple platform architectures, " + + "but can only have one for this model ([" + + modelId + + "]); " + + "expected [" + + requiredArch + + "]; but was " + + architectures + + ""; + + Throwable exception = expectThrows( + IllegalStateException.class, + "Expected IllegalStateException but no exception was thrown", + () -> MlPlatformArchitecturesUtil.verifyMlNodesAndModelArchitectures(architectures, requiredArch, modelId) + ); + assertEquals(exception.getMessage(), message); + } + + public void testVerifyArchitectureMatchesModelPlatformArchitecture_GivenRequiredArchMatches_ThenNothing() { + var requiredArch = randomAlphaOfLength(10); + + var modelId = randomAlphaOfLength(10); + + MlPlatformArchitecturesUtil.verifyMlNodesAndModelArchitectures( + new HashSet<>(Collections.singleton(requiredArch)), + requiredArch, + modelId + ); + } + + public void testVerifyArchitectureMatchesModelPlatformArchitecture_GivenRequiredArchDoesNotMatch_ThenThrowsIAE() { + var requiredArch = randomAlphaOfLength(10); + String architecturesStr = requiredArch + "-DIFFERENT"; + + var modelId = randomAlphaOfLength(10); + String message = "The model being deployed ([" + + modelId + + "]) is platform specific and incompatible with ML nodes in the cluster; " + + "expected [" + + requiredArch + + "]; but was [" + + architecturesStr + + "]"; + + Throwable exception = expectThrows( + IllegalArgumentException.class, + "Expected IllegalArgumentException but no exception was thrown", + () -> MlPlatformArchitecturesUtil.verifyMlNodesAndModelArchitectures(Set.of(architecturesStr), requiredArch, modelId) + ); + assertEquals(exception.getMessage(), message); + } + + private Set nArchitectures(Integer n) { + Set architectures = new HashSet(n); + for (int i = 0; i < n; i++) { + architectures.add(randomAlphaOfLength(10)); + } + return architectures; + } + + private List randomNodeInfos(int max) { + assertTrue(max > 0); + int n = randomInt(max); + List nodeInfos = new ArrayList<>(n); + for (int i = 0; i < n; i++) { + nodeInfos.add(mockNodeInfo()); + } + return nodeInfos; + } + + private NodeInfo mockNodeInfo() { + var mockNodeInfo = mock(NodeInfo.class); + var mockDiscoveryNode = mock(DiscoveryNode.class); + when(mockNodeInfo.getNode()).thenReturn(mockDiscoveryNode); + when(mockDiscoveryNode.hasRole(DiscoveryNodeRole.ML_ROLE.roleName())).thenReturn(randomBoolean()); + var mockOsInfo = mock(OsInfo.class); + when(mockNodeInfo.getInfo(OsInfo.class)).thenReturn(mockOsInfo); + when(mockOsInfo.getArch()).thenReturn(randomAlphaOfLength(10)); + when(mockOsInfo.getName()).thenReturn(randomAlphaOfLength(10)); + + return mockNodeInfo; + } + + protected void assertAsync( + Consumer> function, + T expected, + CheckedConsumer onAnswer, + Consumer onException + ) throws InterruptedException { + + CountDownLatch latch = new CountDownLatch(1); + + LatchedActionListener listener = new LatchedActionListener<>(ActionListener.wrap(r -> { + if (expected == null) { + fail("expected an exception but got a response"); + } else { + assertThat(r, equalTo(expected)); + } + if (onAnswer != null) { + onAnswer.accept(r); + } + }, e -> { + if (onException == null) { + logger.error("got unexpected exception", e); + fail("got unexpected exception: " + e.getMessage()); + } else { + onException.accept(e); + } + }), latch); + + function.accept(listener); + latch.countDown(); + assertTrue("timed out after 20s", latch.await(20, TimeUnit.SECONDS)); + } +} diff --git a/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/test/MockAppender.java b/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/test/MockAppender.java new file mode 100644 index 0000000000000..99c3c58f4ee81 --- /dev/null +++ b/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/test/MockAppender.java @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.ml.test; + +import org.apache.logging.log4j.core.LogEvent; +import org.apache.logging.log4j.core.appender.AbstractAppender; +import org.apache.logging.log4j.core.filter.RegexFilter; +import org.apache.logging.log4j.message.Message; + +public class MockAppender extends AbstractAppender { + public LogEvent lastEvent; + + public MockAppender(final String name) throws IllegalAccessException { + super(name, RegexFilter.createFilter(".*(\n.*)*", new String[0], false, null, null), null, false); + } + + @Override + public void append(LogEvent event) { + lastEvent = event.toImmutable(); + } + + Message lastMessage() { + return lastEvent.getMessage(); + } + + public LogEvent getLastEventAndReset() { + LogEvent toReturn = lastEvent; + lastEvent = null; + return toReturn; + } +} diff --git a/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/ml/inference_crud.yml b/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/ml/inference_crud.yml index 417c52e391b7d..b38c6857108cc 100644 --- a/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/ml/inference_crud.yml +++ b/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/ml/inference_crud.yml @@ -1214,3 +1214,30 @@ setup: ml.get_trained_models: model_id: a-regression-model-0 include: definition_status + +--- +"Test include model platform architecture": + - do: + ml.put_trained_model: + model_id: model-without-definition + body: > + { + "model_type": "pytorch", + "inference_config": { + "ner": { + } + }, + "platform_architecture": "windows-x86_64" + } + + - do: + ml.get_trained_models: + model_id: model-without-definition + include: definition_status + - match: { count: 1 } + - match: { trained_model_configs.0.fully_defined: false } + - do: + ml.get_trained_models: + model_id: model-without-definition + - match: { count: 1 } + - match: { trained_model_configs.0.platform_architecture: windows-x86_64 } From 0ac04ccdd027553a434f2ed3897b5663b35c5d04 Mon Sep 17 00:00:00 2001 From: Nik Everett Date: Thu, 28 Sep 2023 14:19:50 -0400 Subject: [PATCH 19/32] ESQL: Don't build Pages out of closed Blocks (#99980) This makes sure none of the `Block`s passed to the ctor of `Page` are closed. We never expect to do that. And you can't read the `Block` from the `Page` anyway. --- .../java/org/elasticsearch/compute/data/Page.java | 8 ++++---- .../elasticsearch/compute/data/BasicBlockTests.java | 12 +++++++++--- .../compute/data/BlockFactoryTests.java | 7 +++++-- .../elasticsearch/compute/data/DocVectorTests.java | 12 ++++++++---- 4 files changed, 26 insertions(+), 13 deletions(-) diff --git a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/Page.java b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/Page.java index 873565592dfaf..a4c89422213b1 100644 --- a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/Page.java +++ b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/Page.java @@ -10,7 +10,6 @@ import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.io.stream.Writeable; -import org.elasticsearch.core.Assertions; import org.elasticsearch.core.Releasables; import java.io.IOException; @@ -69,9 +68,10 @@ private Page(boolean copyBlocks, int positionCount, Block[] blocks) { // assert assertPositionCount(blocks); this.positionCount = positionCount; this.blocks = copyBlocks ? blocks.clone() : blocks; - if (Assertions.ENABLED) { - for (Block b : blocks) { - assert b.getPositionCount() == positionCount : "expected positionCount=" + positionCount + " but was " + b; + for (Block b : blocks) { + assert b.getPositionCount() == positionCount : "expected positionCount=" + positionCount + " but was " + b; + if (b.isReleased()) { + throw new IllegalArgumentException("can't build page out of released blocks but [" + b + "] was released"); } } } diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/BasicBlockTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/BasicBlockTests.java index cf7fbbea1c775..f99ded96a9984 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/BasicBlockTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/BasicBlockTests.java @@ -906,10 +906,12 @@ void assertZeroPositionsAndRelease(Vector vector) { void releaseAndAssertBreaker(Block... blocks) { assertThat(breaker.getUsed(), greaterThan(0L)); + Page[] pages = Arrays.stream(blocks).map(Page::new).toArray(Page[]::new); Releasables.closeExpectNoException(blocks); Arrays.stream(blocks).forEach(block -> assertThat(block.isReleased(), is(true))); Arrays.stream(blocks).forEach(BasicBlockTests::assertCannotDoubleRelease); - Arrays.stream(blocks).forEach(BasicBlockTests::assertCannotReadFromPage); + Arrays.stream(pages).forEach(BasicBlockTests::assertCannotReadFromPage); + Arrays.stream(blocks).forEach(BasicBlockTests::assertCannotAddToPage); assertThat(breaker.getUsed(), is(0L)); } @@ -924,12 +926,16 @@ static void assertCannotDoubleRelease(Block block) { assertThat(ex.getMessage(), containsString("can't release already released block")); } - static void assertCannotReadFromPage(Block block) { - Page page = new Page(block); + static void assertCannotReadFromPage(Page page) { var e = expectThrows(IllegalStateException.class, () -> page.getBlock(0)); assertThat(e.getMessage(), containsString("can't read released block")); } + static void assertCannotAddToPage(Block block) { + var e = expectThrows(IllegalArgumentException.class, () -> new Page(block)); + assertThat(e.getMessage(), containsString("can't build page out of released blocks but")); + } + static int randomPosition(int positionCount) { return positionCount == 1 ? 0 : randomIntBetween(0, positionCount - 1); } diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/BlockFactoryTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/BlockFactoryTests.java index 24d4e27e92ad1..7be79e73b5d9d 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/BlockFactoryTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/BlockFactoryTests.java @@ -573,13 +573,16 @@ static Block.MvOrdering randomOrdering() { } void releaseAndAssertBreaker(T data) { + Page page = data instanceof Block block ? new Page(block) : null; assertThat(breaker.getUsed(), greaterThan(0L)); Releasables.closeExpectNoException(data); if (data instanceof Block block) { assertThat(block.isReleased(), is(true)); - Page page = new Page(block); - var e = expectThrows(IllegalStateException.class, () -> page.getBlock(0)); + Exception e = expectThrows(IllegalStateException.class, () -> page.getBlock(0)); assertThat(e.getMessage(), containsString("can't read released block")); + + e = expectThrows(IllegalArgumentException.class, () -> new Page(block)); + assertThat(e.getMessage(), containsString("can't build page out of released blocks")); } assertThat(breaker.getUsed(), is(0L)); } diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/DocVectorTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/DocVectorTests.java index 0f76e024c7436..465dc95a15ea4 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/DocVectorTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/data/DocVectorTests.java @@ -136,14 +136,18 @@ public void testCannotDoubleRelease() { var block = new DocVector(IntVector.range(0, 2), IntBlock.newConstantBlockWith(0, 2).asVector(), IntVector.range(0, 2), null) .asBlock(); assertThat(block.isReleased(), is(false)); + Page page = new Page(block); + Releasables.closeExpectNoException(block); assertThat(block.isReleased(), is(true)); - var ex = expectThrows(IllegalStateException.class, () -> block.close()); - assertThat(ex.getMessage(), containsString("can't release already released block")); + Exception e = expectThrows(IllegalStateException.class, () -> block.close()); + assertThat(e.getMessage(), containsString("can't release already released block")); - Page page = new Page(block); - var e = expectThrows(IllegalStateException.class, () -> page.getBlock(0)); + e = expectThrows(IllegalStateException.class, () -> page.getBlock(0)); assertThat(e.getMessage(), containsString("can't read released block")); + + e = expectThrows(IllegalArgumentException.class, () -> new Page(block)); + assertThat(e.getMessage(), containsString("can't build page out of released blocks")); } } From c879e54592f388dc19b3de076c318e3b7649ef09 Mon Sep 17 00:00:00 2001 From: Carlos Delgado <6339205+carlosdelest@users.noreply.github.com> Date: Thu, 28 Sep 2023 20:43:06 +0200 Subject: [PATCH 20/32] Allow text_expansion query to use sparse_vector field type (#99910) --- .../ml/integration/TextExpansionQueryIT.java | 56 ++++++--- .../text_expansion_search_rank_features.yml | 111 ++++++++++++++++++ .../text_expansion_search_sparse_vector.yml | 111 ++++++++++++++++++ 3 files changed, 262 insertions(+), 16 deletions(-) create mode 100644 x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/ml/text_expansion_search_rank_features.yml create mode 100644 x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/ml/text_expansion_search_sparse_vector.yml diff --git a/x-pack/plugin/ml/qa/native-multi-node-tests/src/javaRestTest/java/org/elasticsearch/xpack/ml/integration/TextExpansionQueryIT.java b/x-pack/plugin/ml/qa/native-multi-node-tests/src/javaRestTest/java/org/elasticsearch/xpack/ml/integration/TextExpansionQueryIT.java index 7ae7d4b0497e0..dbf489e8abf23 100644 --- a/x-pack/plugin/ml/qa/native-multi-node-tests/src/javaRestTest/java/org/elasticsearch/xpack/ml/integration/TextExpansionQueryIT.java +++ b/x-pack/plugin/ml/qa/native-multi-node-tests/src/javaRestTest/java/org/elasticsearch/xpack/ml/integration/TextExpansionQueryIT.java @@ -103,8 +103,16 @@ public class TextExpansionQueryIT extends PyTorchModelRestTestCase { RAW_MODEL_SIZE = Base64.getDecoder().decode(BASE_64_ENCODED_MODEL).length; } + public void testRankFeaturesTextExpansionQuery() throws IOException { + testTextExpansionQuery("rank_features"); + } + + public void testSparseVectorTextExpansionQuery() throws IOException { + testTextExpansionQuery("sparse_vector"); + } + @SuppressWarnings("unchecked") - public void testTextExpansionQuery() throws IOException { + private void testTextExpansionQuery(String tokensFieldType) throws IOException { String modelId = "text-expansion-test"; String indexName = modelId + "-index"; @@ -140,7 +148,7 @@ public void testTextExpansionQuery() throws IOException { } // index tokens - createRankFeaturesIndex(indexName); + createIndex(indexName, tokensFieldType); bulkIndexDocs(inputs, tokenWeights, indexName); // Test text expansion search against the indexed rank features @@ -157,7 +165,15 @@ public void testTextExpansionQuery() throws IOException { } } - public void testWithPipelineIngest() throws IOException { + public void testRankFeaturesWithPipelineIngest() throws IOException { + testWithPipelineIngest("rank_features"); + } + + public void testSparseVectorWithPipelineIngest() throws IOException { + testWithPipelineIngest("sparse_vector"); + } + + private void testWithPipelineIngest(String tokensFieldType) throws IOException { String modelId = "text-expansion-pipeline-test"; String indexName = modelId + "-index"; @@ -182,7 +198,7 @@ public void testWithPipelineIngest() throws IOException { ); // index tokens - createRankFeaturesIndex(indexName); + createIndex(indexName, tokensFieldType); var pipelineId = putPipeline(modelId); bulkIndexThroughPipeline(inputs, indexName, pipelineId); @@ -201,7 +217,15 @@ public void testWithPipelineIngest() throws IOException { } } - public void testWithDotsInTokenNames() throws IOException { + public void testRankFeaturesWithDotsInTokenNames() throws IOException { + testWithDotsInTokenNames("rank_features"); + } + + public void testSparseVectorWithDotsInTokenNames() throws IOException { + testWithDotsInTokenNames("sparse_vector"); + } + + private void testWithDotsInTokenNames(String tokensFieldType) throws IOException { String modelId = "text-expansion-dots-in-tokens"; String indexName = modelId + "-index"; @@ -214,7 +238,7 @@ public void testWithDotsInTokenNames() throws IOException { List inputs = List.of("these are my words."); // index tokens - createRankFeaturesIndex(indexName); + createIndex(indexName, tokensFieldType); var pipelineId = putPipeline(modelId); bulkIndexThroughPipeline(inputs, indexName, pipelineId); @@ -278,18 +302,18 @@ protected void createTextExpansionModel(String modelId) throws IOException { client().performRequest(request); } - private void createRankFeaturesIndex(String indexName) throws IOException { + private void createIndex(String indexName, String tokensFieldType) throws IOException { Request createIndex = new Request("PUT", "/" + indexName); createIndex.setJsonEntity(""" - { - "mappings": { - "properties": { - "text_field": { - "type": "text" - }, - "ml.tokens": { - "type": "rank_features" - } + { + "mappings": { + "properties": { + "text_field": { + "type": "text" + }, + "ml.tokens": { + """ + "\"type\": \"" + tokensFieldType + "\"" + """ + } } } }"""); diff --git a/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/ml/text_expansion_search_rank_features.yml b/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/ml/text_expansion_search_rank_features.yml new file mode 100644 index 0000000000000..28a6ad826bc64 --- /dev/null +++ b/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/ml/text_expansion_search_rank_features.yml @@ -0,0 +1,111 @@ +# This test uses the simple model defined in +# TextExpansionQueryIT.java to create the token weights. +setup: + - skip: + version: ' - 8.10.99' + reason: "sparse_vector field type reintroduced in 8.11" + features: headers + - do: + headers: + Authorization: "Basic eF9wYWNrX3Jlc3RfdXNlcjp4LXBhY2stdGVzdC1wYXNzd29yZA==" # run as x_pack_rest_user, i.e. the test setup superuser + indices.create: + index: index-with-rank-features + body: + mappings: + properties: + source_text: + type: keyword + ml.tokens: + type: rank_features + + - do: + headers: + Authorization: "Basic eF9wYWNrX3Jlc3RfdXNlcjp4LXBhY2stdGVzdC1wYXNzd29yZA==" # run as x_pack_rest_user, i.e. the test setup superuser + indices.create: + index: unrelated + body: + mappings: + properties: + source_text: + type: keyword + + - do: + headers: + Authorization: "Basic eF9wYWNrX3Jlc3RfdXNlcjp4LXBhY2stdGVzdC1wYXNzd29yZA==" # run as x_pack_rest_user, i.e. the test setup superuser + ml.put_trained_model: + model_id: "text_expansion_model" + body: > + { + "description": "simple model for testing", + "model_type": "pytorch", + "inference_config": { + "text_expansion": { + "tokenization": { + "bert": { + "with_special_tokens": false + } + } + } + } + } + - do: + headers: + Authorization: "Basic eF9wYWNrX3Jlc3RfdXNlcjp4LXBhY2stdGVzdC1wYXNzd29yZA==" # run as x_pack_rest_user, i.e. the test setup superuser + ml.put_trained_model_vocabulary: + model_id: "text_expansion_model" + body: > + { "vocabulary": ["[PAD]", "[UNK]", "these", "are", "my", "words", "the", "washing", "machine", "is", "leaking", "octopus", "comforter", "smells"] } + - do: + headers: + Authorization: "Basic eF9wYWNrX3Jlc3RfdXNlcjp4LXBhY2stdGVzdC1wYXNzd29yZA==" # run as x_pack_rest_user, i.e. the test setup superuser + ml.put_trained_model_definition_part: + model_id: "text_expansion_model" + part: 0 + body: > + { + "total_definition_length":2078, + "definition": "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", + "total_parts": 1 + } + - do: + headers: + Authorization: "Basic eF9wYWNrX3Jlc3RfdXNlcjp4LXBhY2stdGVzdC1wYXNzd29yZA==" # run as x_pack_rest_user, i.e. the test setup superuser + Content-Type: application/json + bulk: + index: index-with-rank-features + refresh: true + body: | + {"index": {}} + {"source_text": "my words comforter", "ml.tokens":{"my":1.0, "words":1.0,"comforter":1.0}} + {"index": {}} + {"source_text": "the machine is leaking", "ml.tokens":{"the":1.0,"machine":1.0,"is":1.0,"leaking":1.0}} + {"index": {}} + {"source_text": "these are my words", "ml.tokens":{"these":1.0,"are":1.0,"my":1.0,"words":1.0}} + {"index": {}} + {"source_text": "the octopus comforter smells", "ml.tokens":{"the":1.0,"octopus":1.0,"comforter":1.0,"smells":1.0}} + {"index": {}} + {"source_text": "the octopus comforter is leaking", "ml.tokens":{"the":1.0,"octopus":1.0,"comforter":1.0,"is":1.0,"leaking":1.0}} + {"index": {}} + {"source_text": "washing machine smells", "ml.tokens":{"washing":1.0,"machine":1.0,"smells":1.0}} + + - do: + headers: + Authorization: "Basic eF9wYWNrX3Jlc3RfdXNlcjp4LXBhY2stdGVzdC1wYXNzd29yZA==" # run as x_pack_rest_user, i.e. the test setup superuser + Content-Type: application/json + ml.start_trained_model_deployment: + model_id: text_expansion_model + wait_for: started + +--- +"Test text expansion search": + - do: + search: + index: index-with-rank-features + body: + query: + text_expansion: + ml.tokens: + model_id: text_expansion_model + model_text: "octopus comforter smells" + - match: { hits.total.value: 4 } + - match: { hits.hits.0._source.source_text: "the octopus comforter smells" } diff --git a/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/ml/text_expansion_search_sparse_vector.yml b/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/ml/text_expansion_search_sparse_vector.yml new file mode 100644 index 0000000000000..5a31af18f8269 --- /dev/null +++ b/x-pack/plugin/src/yamlRestTest/resources/rest-api-spec/test/ml/text_expansion_search_sparse_vector.yml @@ -0,0 +1,111 @@ +# This test uses the simple model defined in +# TextExpansionQueryIT.java to create the token weights. +setup: + - skip: + features: headers + version: ' - 8.7.99' + reason: "text_expansion query introduced in 8.8" + - do: + headers: + Authorization: "Basic eF9wYWNrX3Jlc3RfdXNlcjp4LXBhY2stdGVzdC1wYXNzd29yZA==" # run as x_pack_rest_user, i.e. the test setup superuser + indices.create: + index: index-with-rank-features + body: + mappings: + properties: + source_text: + type: keyword + ml.tokens: + type: sparse_vector + + - do: + headers: + Authorization: "Basic eF9wYWNrX3Jlc3RfdXNlcjp4LXBhY2stdGVzdC1wYXNzd29yZA==" # run as x_pack_rest_user, i.e. the test setup superuser + indices.create: + index: unrelated + body: + mappings: + properties: + source_text: + type: keyword + + - do: + headers: + Authorization: "Basic eF9wYWNrX3Jlc3RfdXNlcjp4LXBhY2stdGVzdC1wYXNzd29yZA==" # run as x_pack_rest_user, i.e. the test setup superuser + ml.put_trained_model: + model_id: "text_expansion_model" + body: > + { + "description": "simple model for testing", + "model_type": "pytorch", + "inference_config": { + "text_expansion": { + "tokenization": { + "bert": { + "with_special_tokens": false + } + } + } + } + } + - do: + headers: + Authorization: "Basic eF9wYWNrX3Jlc3RfdXNlcjp4LXBhY2stdGVzdC1wYXNzd29yZA==" # run as x_pack_rest_user, i.e. the test setup superuser + ml.put_trained_model_vocabulary: + model_id: "text_expansion_model" + body: > + { "vocabulary": ["[PAD]", "[UNK]", "these", "are", "my", "words", "the", "washing", "machine", "is", "leaking", "octopus", "comforter", "smells"] } + - do: + headers: + Authorization: "Basic eF9wYWNrX3Jlc3RfdXNlcjp4LXBhY2stdGVzdC1wYXNzd29yZA==" # run as x_pack_rest_user, i.e. the test setup superuser + ml.put_trained_model_definition_part: + model_id: "text_expansion_model" + part: 0 + body: > + { + "total_definition_length":2078, + "definition": "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", + "total_parts": 1 + } + - do: + headers: + Authorization: "Basic eF9wYWNrX3Jlc3RfdXNlcjp4LXBhY2stdGVzdC1wYXNzd29yZA==" # run as x_pack_rest_user, i.e. the test setup superuser + Content-Type: application/json + bulk: + index: index-with-rank-features + refresh: true + body: | + {"index": {}} + {"source_text": "my words comforter", "ml.tokens":{"my":1.0, "words":1.0,"comforter":1.0}} + {"index": {}} + {"source_text": "the machine is leaking", "ml.tokens":{"the":1.0,"machine":1.0,"is":1.0,"leaking":1.0}} + {"index": {}} + {"source_text": "these are my words", "ml.tokens":{"these":1.0,"are":1.0,"my":1.0,"words":1.0}} + {"index": {}} + {"source_text": "the octopus comforter smells", "ml.tokens":{"the":1.0,"octopus":1.0,"comforter":1.0,"smells":1.0}} + {"index": {}} + {"source_text": "the octopus comforter is leaking", "ml.tokens":{"the":1.0,"octopus":1.0,"comforter":1.0,"is":1.0,"leaking":1.0}} + {"index": {}} + {"source_text": "washing machine smells", "ml.tokens":{"washing":1.0,"machine":1.0,"smells":1.0}} + + - do: + headers: + Authorization: "Basic eF9wYWNrX3Jlc3RfdXNlcjp4LXBhY2stdGVzdC1wYXNzd29yZA==" # run as x_pack_rest_user, i.e. the test setup superuser + Content-Type: application/json + ml.start_trained_model_deployment: + model_id: text_expansion_model + wait_for: started + +--- +"Test text expansion search": + - do: + search: + index: index-with-rank-features + body: + query: + text_expansion: + ml.tokens: + model_id: text_expansion_model + model_text: "octopus comforter smells" + - match: { hits.total.value: 4 } + - match: { hits.hits.0._source.source_text: "the octopus comforter smells" } From 163b5eff5a6a580975efc7206460e0dd54614de3 Mon Sep 17 00:00:00 2001 From: Nhat Nguyen Date: Thu, 28 Sep 2023 12:41:39 -0700 Subject: [PATCH 21/32] Add deduplicated attribute to MvOrdering (#100027) Today, we have the ability to specify whether multivalued fields are sorted in ascending order or not. This feature allows operators like topn to enable optimizations. However, we are currently missing the deduplicated attribute. If multivalued fields are deduplicated at each position, we can further optimize operators such as hash and mv_dedup. In fact, blocks should not have mv_ascending property alone; it always goes together with mv_deduplicated. Additionally, mv_dedup or hash should generate blocks that have only the mv_dedup property. --- .../compute/operator/EvalBenchmark.java | 2 +- .../compute/gen/MvEvaluatorImplementer.java | 2 +- .../operator/MultivalueDedupeBytesRef.java | 6 ++-- .../operator/MultivalueDedupeDouble.java | 6 ++-- .../compute/operator/MultivalueDedupeInt.java | 6 ++-- .../operator/MultivalueDedupeLong.java | 6 ++-- .../operator/topn/KeyExtractorForBoolean.java | 5 ++- .../topn/KeyExtractorForBytesRef.java | 5 ++- .../operator/topn/KeyExtractorForDouble.java | 5 ++- .../operator/topn/KeyExtractorForInt.java | 5 ++- .../operator/topn/KeyExtractorForLong.java | 5 ++- .../blockhash/DoubleBlockHash.java | 3 +- .../aggregation/blockhash/IntBlockHash.java | 2 +- .../aggregation/blockhash/LongBlockHash.java | 3 +- .../compute/data/AbstractVectorBlock.java | 2 +- .../org/elasticsearch/compute/data/Block.java | 32 ++++++++++++++++--- .../compute/data/BlockUtils.java | 20 ++++++++++-- .../compute/lucene/BlockDocValuesReader.java | 18 +++++------ .../operator/X-MultivalueDedupe.java.st | 6 ++-- .../operator/topn/X-KeyExtractor.java.st | 5 ++- .../ValuesSourceReaderOperatorTests.java | 10 +++--- .../operator/topn/TopNOperatorTests.java | 8 ++--- .../multivalue/MvMaxBooleanEvaluator.java | 4 +-- .../multivalue/MvMaxBytesRefEvaluator.java | 4 +-- .../multivalue/MvMaxDoubleEvaluator.java | 4 +-- .../scalar/multivalue/MvMaxIntEvaluator.java | 4 +-- .../scalar/multivalue/MvMaxLongEvaluator.java | 4 +-- .../multivalue/MvMedianIntEvaluator.java | 4 +-- .../multivalue/MvMedianLongEvaluator.java | 4 +-- .../MvMedianUnsignedLongEvaluator.java | 4 +-- .../multivalue/MvMinBooleanEvaluator.java | 4 +-- .../multivalue/MvMinBytesRefEvaluator.java | 4 +-- .../multivalue/MvMinDoubleEvaluator.java | 4 +-- .../scalar/multivalue/MvMinIntEvaluator.java | 4 +-- .../scalar/multivalue/MvMinLongEvaluator.java | 4 +-- .../AbstractMultivalueFunctionTestCase.java | 14 +++++++- 36 files changed, 137 insertions(+), 91 deletions(-) diff --git a/benchmarks/src/main/java/org/elasticsearch/benchmark/compute/operator/EvalBenchmark.java b/benchmarks/src/main/java/org/elasticsearch/benchmark/compute/operator/EvalBenchmark.java index 82c9416515d24..e129cdaa12469 100644 --- a/benchmarks/src/main/java/org/elasticsearch/benchmark/compute/operator/EvalBenchmark.java +++ b/benchmarks/src/main/java/org/elasticsearch/benchmark/compute/operator/EvalBenchmark.java @@ -229,7 +229,7 @@ private static Page page(String operation) { case "mv_min", "mv_min_ascending" -> { var builder = LongBlock.newBlockBuilder(BLOCK_LENGTH); if (operation.endsWith("ascending")) { - builder.mvOrdering(Block.MvOrdering.ASCENDING); + builder.mvOrdering(Block.MvOrdering.DEDUPLICATED_AND_SORTED_ASCENDING); } for (int i = 0; i < BLOCK_LENGTH; i++) { builder.beginPositionEntry(); diff --git a/x-pack/plugin/esql/compute/gen/src/main/java/org/elasticsearch/compute/gen/MvEvaluatorImplementer.java b/x-pack/plugin/esql/compute/gen/src/main/java/org/elasticsearch/compute/gen/MvEvaluatorImplementer.java index 86ae6d3f46789..0e29cc7673fee 100644 --- a/x-pack/plugin/esql/compute/gen/src/main/java/org/elasticsearch/compute/gen/MvEvaluatorImplementer.java +++ b/x-pack/plugin/esql/compute/gen/src/main/java/org/elasticsearch/compute/gen/MvEvaluatorImplementer.java @@ -261,7 +261,7 @@ private MethodSpec eval(String name, boolean nullable) { if (ascendingFunction == null) { return; } - builder.beginControlFlow("if (fieldVal.mvOrdering() == Block.MvOrdering.ASCENDING)"); + builder.beginControlFlow("if (fieldVal.mvSortedAscending())"); builder.addStatement("return $L(fieldVal)", name.replace("eval", "evalAscending")); builder.endControlFlow(); }, builder -> { diff --git a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/operator/MultivalueDedupeBytesRef.java b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/operator/MultivalueDedupeBytesRef.java index 48aec38b800ce..89c15d9eeab72 100644 --- a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/operator/MultivalueDedupeBytesRef.java +++ b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/operator/MultivalueDedupeBytesRef.java @@ -44,7 +44,7 @@ public MultivalueDedupeBytesRef(BytesRefBlock block) { * {@link Block} using an adaptive algorithm based on the size of the input list. */ public BytesRefBlock dedupeToBlockAdaptive() { - if (false == block.mayHaveMultivaluedFields()) { + if (block.mvDeduplicated()) { return block; } BytesRefBlock.Builder builder = BytesRefBlock.newBlockBuilder(block.getPositionCount()); @@ -92,7 +92,7 @@ public BytesRefBlock dedupeToBlockAdaptive() { * which picks based on the number of elements at each position. */ public BytesRefBlock dedupeToBlockUsingCopyAndSort() { - if (false == block.mayHaveMultivaluedFields()) { + if (block.mvDeduplicated()) { return block; } BytesRefBlock.Builder builder = BytesRefBlock.newBlockBuilder(block.getPositionCount()); @@ -120,7 +120,7 @@ public BytesRefBlock dedupeToBlockUsingCopyAndSort() { * {@link #dedupeToBlockAdaptive} unless you need the results sorted. */ public BytesRefBlock dedupeToBlockUsingCopyMissing() { - if (false == block.mayHaveMultivaluedFields()) { + if (block.mvDeduplicated()) { return block; } BytesRefBlock.Builder builder = BytesRefBlock.newBlockBuilder(block.getPositionCount()); diff --git a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/operator/MultivalueDedupeDouble.java b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/operator/MultivalueDedupeDouble.java index d30292f6fa32c..22f5cef2d57d8 100644 --- a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/operator/MultivalueDedupeDouble.java +++ b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/operator/MultivalueDedupeDouble.java @@ -41,7 +41,7 @@ public MultivalueDedupeDouble(DoubleBlock block) { * {@link Block} using an adaptive algorithm based on the size of the input list. */ public DoubleBlock dedupeToBlockAdaptive() { - if (false == block.mayHaveMultivaluedFields()) { + if (block.mvDeduplicated()) { return block; } DoubleBlock.Builder builder = DoubleBlock.newBlockBuilder(block.getPositionCount()); @@ -89,7 +89,7 @@ public DoubleBlock dedupeToBlockAdaptive() { * which picks based on the number of elements at each position. */ public DoubleBlock dedupeToBlockUsingCopyAndSort() { - if (false == block.mayHaveMultivaluedFields()) { + if (block.mvDeduplicated()) { return block; } DoubleBlock.Builder builder = DoubleBlock.newBlockBuilder(block.getPositionCount()); @@ -117,7 +117,7 @@ public DoubleBlock dedupeToBlockUsingCopyAndSort() { * {@link #dedupeToBlockAdaptive} unless you need the results sorted. */ public DoubleBlock dedupeToBlockUsingCopyMissing() { - if (false == block.mayHaveMultivaluedFields()) { + if (block.mvDeduplicated()) { return block; } DoubleBlock.Builder builder = DoubleBlock.newBlockBuilder(block.getPositionCount()); diff --git a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/operator/MultivalueDedupeInt.java b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/operator/MultivalueDedupeInt.java index cda9308a7e6d2..be6d08cfc39d7 100644 --- a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/operator/MultivalueDedupeInt.java +++ b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/operator/MultivalueDedupeInt.java @@ -40,7 +40,7 @@ public MultivalueDedupeInt(IntBlock block) { * {@link Block} using an adaptive algorithm based on the size of the input list. */ public IntBlock dedupeToBlockAdaptive() { - if (false == block.mayHaveMultivaluedFields()) { + if (block.mvDeduplicated()) { return block; } IntBlock.Builder builder = IntBlock.newBlockBuilder(block.getPositionCount()); @@ -88,7 +88,7 @@ public IntBlock dedupeToBlockAdaptive() { * which picks based on the number of elements at each position. */ public IntBlock dedupeToBlockUsingCopyAndSort() { - if (false == block.mayHaveMultivaluedFields()) { + if (block.mvDeduplicated()) { return block; } IntBlock.Builder builder = IntBlock.newBlockBuilder(block.getPositionCount()); @@ -116,7 +116,7 @@ public IntBlock dedupeToBlockUsingCopyAndSort() { * {@link #dedupeToBlockAdaptive} unless you need the results sorted. */ public IntBlock dedupeToBlockUsingCopyMissing() { - if (false == block.mayHaveMultivaluedFields()) { + if (block.mvDeduplicated()) { return block; } IntBlock.Builder builder = IntBlock.newBlockBuilder(block.getPositionCount()); diff --git a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/operator/MultivalueDedupeLong.java b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/operator/MultivalueDedupeLong.java index 0266131fba37c..d4da43f93d503 100644 --- a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/operator/MultivalueDedupeLong.java +++ b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/operator/MultivalueDedupeLong.java @@ -42,7 +42,7 @@ public MultivalueDedupeLong(LongBlock block) { * {@link Block} using an adaptive algorithm based on the size of the input list. */ public LongBlock dedupeToBlockAdaptive() { - if (false == block.mayHaveMultivaluedFields()) { + if (block.mvDeduplicated()) { return block; } LongBlock.Builder builder = LongBlock.newBlockBuilder(block.getPositionCount()); @@ -90,7 +90,7 @@ public LongBlock dedupeToBlockAdaptive() { * which picks based on the number of elements at each position. */ public LongBlock dedupeToBlockUsingCopyAndSort() { - if (false == block.mayHaveMultivaluedFields()) { + if (block.mvDeduplicated()) { return block; } LongBlock.Builder builder = LongBlock.newBlockBuilder(block.getPositionCount()); @@ -118,7 +118,7 @@ public LongBlock dedupeToBlockUsingCopyAndSort() { * {@link #dedupeToBlockAdaptive} unless you need the results sorted. */ public LongBlock dedupeToBlockUsingCopyMissing() { - if (false == block.mayHaveMultivaluedFields()) { + if (block.mvDeduplicated()) { return block; } LongBlock.Builder builder = LongBlock.newBlockBuilder(block.getPositionCount()); diff --git a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/operator/topn/KeyExtractorForBoolean.java b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/operator/topn/KeyExtractorForBoolean.java index 0bdc5ac620eb0..40fe7ffdde661 100644 --- a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/operator/topn/KeyExtractorForBoolean.java +++ b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/operator/topn/KeyExtractorForBoolean.java @@ -7,7 +7,6 @@ package org.elasticsearch.compute.operator.topn; -import org.elasticsearch.compute.data.Block; import org.elasticsearch.compute.data.BooleanBlock; import org.elasticsearch.compute.data.BooleanVector; import org.elasticsearch.compute.operator.BreakingBytesRefBuilder; @@ -19,11 +18,11 @@ static KeyExtractorForBoolean extractorFor(TopNEncoder encoder, boolean ascendin return new KeyExtractorForBoolean.ForVector(encoder, nul, nonNul, v); } if (ascending) { - return block.mvOrdering() == Block.MvOrdering.ASCENDING + return block.mvSortedAscending() ? new KeyExtractorForBoolean.MinForAscending(encoder, nul, nonNul, block) : new KeyExtractorForBoolean.MinForUnordered(encoder, nul, nonNul, block); } - return block.mvOrdering() == Block.MvOrdering.ASCENDING + return block.mvSortedAscending() ? new KeyExtractorForBoolean.MaxForAscending(encoder, nul, nonNul, block) : new KeyExtractorForBoolean.MaxForUnordered(encoder, nul, nonNul, block); } diff --git a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/operator/topn/KeyExtractorForBytesRef.java b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/operator/topn/KeyExtractorForBytesRef.java index accce46f38e30..2f546a46aaeaf 100644 --- a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/operator/topn/KeyExtractorForBytesRef.java +++ b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/operator/topn/KeyExtractorForBytesRef.java @@ -8,7 +8,6 @@ package org.elasticsearch.compute.operator.topn; import org.apache.lucene.util.BytesRef; -import org.elasticsearch.compute.data.Block; import org.elasticsearch.compute.data.BytesRefBlock; import org.elasticsearch.compute.data.BytesRefVector; import org.elasticsearch.compute.operator.BreakingBytesRefBuilder; @@ -20,11 +19,11 @@ static KeyExtractorForBytesRef extractorFor(TopNEncoder encoder, boolean ascendi return new KeyExtractorForBytesRef.ForVector(encoder, nul, nonNul, v); } if (ascending) { - return block.mvOrdering() == Block.MvOrdering.ASCENDING + return block.mvSortedAscending() ? new KeyExtractorForBytesRef.MinForAscending(encoder, nul, nonNul, block) : new KeyExtractorForBytesRef.MinForUnordered(encoder, nul, nonNul, block); } - return block.mvOrdering() == Block.MvOrdering.ASCENDING + return block.mvSortedAscending() ? new KeyExtractorForBytesRef.MaxForAscending(encoder, nul, nonNul, block) : new KeyExtractorForBytesRef.MaxForUnordered(encoder, nul, nonNul, block); } diff --git a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/operator/topn/KeyExtractorForDouble.java b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/operator/topn/KeyExtractorForDouble.java index 2f2968da16d83..5e821b9e24db5 100644 --- a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/operator/topn/KeyExtractorForDouble.java +++ b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/operator/topn/KeyExtractorForDouble.java @@ -7,7 +7,6 @@ package org.elasticsearch.compute.operator.topn; -import org.elasticsearch.compute.data.Block; import org.elasticsearch.compute.data.DoubleBlock; import org.elasticsearch.compute.data.DoubleVector; import org.elasticsearch.compute.operator.BreakingBytesRefBuilder; @@ -19,11 +18,11 @@ static KeyExtractorForDouble extractorFor(TopNEncoder encoder, boolean ascending return new KeyExtractorForDouble.ForVector(encoder, nul, nonNul, v); } if (ascending) { - return block.mvOrdering() == Block.MvOrdering.ASCENDING + return block.mvSortedAscending() ? new KeyExtractorForDouble.MinForAscending(encoder, nul, nonNul, block) : new KeyExtractorForDouble.MinForUnordered(encoder, nul, nonNul, block); } - return block.mvOrdering() == Block.MvOrdering.ASCENDING + return block.mvSortedAscending() ? new KeyExtractorForDouble.MaxForAscending(encoder, nul, nonNul, block) : new KeyExtractorForDouble.MaxForUnordered(encoder, nul, nonNul, block); } diff --git a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/operator/topn/KeyExtractorForInt.java b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/operator/topn/KeyExtractorForInt.java index 400c43168277d..d4269a622f098 100644 --- a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/operator/topn/KeyExtractorForInt.java +++ b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/operator/topn/KeyExtractorForInt.java @@ -7,7 +7,6 @@ package org.elasticsearch.compute.operator.topn; -import org.elasticsearch.compute.data.Block; import org.elasticsearch.compute.data.IntBlock; import org.elasticsearch.compute.data.IntVector; import org.elasticsearch.compute.operator.BreakingBytesRefBuilder; @@ -19,11 +18,11 @@ static KeyExtractorForInt extractorFor(TopNEncoder encoder, boolean ascending, b return new KeyExtractorForInt.ForVector(encoder, nul, nonNul, v); } if (ascending) { - return block.mvOrdering() == Block.MvOrdering.ASCENDING + return block.mvSortedAscending() ? new KeyExtractorForInt.MinForAscending(encoder, nul, nonNul, block) : new KeyExtractorForInt.MinForUnordered(encoder, nul, nonNul, block); } - return block.mvOrdering() == Block.MvOrdering.ASCENDING + return block.mvSortedAscending() ? new KeyExtractorForInt.MaxForAscending(encoder, nul, nonNul, block) : new KeyExtractorForInt.MaxForUnordered(encoder, nul, nonNul, block); } diff --git a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/operator/topn/KeyExtractorForLong.java b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/operator/topn/KeyExtractorForLong.java index 843efdd95471f..6a200efff529d 100644 --- a/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/operator/topn/KeyExtractorForLong.java +++ b/x-pack/plugin/esql/compute/src/main/generated-src/org/elasticsearch/compute/operator/topn/KeyExtractorForLong.java @@ -7,7 +7,6 @@ package org.elasticsearch.compute.operator.topn; -import org.elasticsearch.compute.data.Block; import org.elasticsearch.compute.data.LongBlock; import org.elasticsearch.compute.data.LongVector; import org.elasticsearch.compute.operator.BreakingBytesRefBuilder; @@ -19,11 +18,11 @@ static KeyExtractorForLong extractorFor(TopNEncoder encoder, boolean ascending, return new KeyExtractorForLong.ForVector(encoder, nul, nonNul, v); } if (ascending) { - return block.mvOrdering() == Block.MvOrdering.ASCENDING + return block.mvSortedAscending() ? new KeyExtractorForLong.MinForAscending(encoder, nul, nonNul, block) : new KeyExtractorForLong.MinForUnordered(encoder, nul, nonNul, block); } - return block.mvOrdering() == Block.MvOrdering.ASCENDING + return block.mvSortedAscending() ? new KeyExtractorForLong.MaxForAscending(encoder, nul, nonNul, block) : new KeyExtractorForLong.MaxForUnordered(encoder, nul, nonNul, block); } diff --git a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/aggregation/blockhash/DoubleBlockHash.java b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/aggregation/blockhash/DoubleBlockHash.java index 3a52beb9c2d87..f3ca16869898f 100644 --- a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/aggregation/blockhash/DoubleBlockHash.java +++ b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/aggregation/blockhash/DoubleBlockHash.java @@ -82,7 +82,8 @@ public DoubleBlock[] getKeys() { } BitSet nulls = new BitSet(1); nulls.set(0); - return new DoubleBlock[] { new DoubleArrayBlock(keys, keys.length, null, nulls, Block.MvOrdering.ASCENDING) }; + return new DoubleBlock[] { + new DoubleArrayBlock(keys, keys.length, null, nulls, Block.MvOrdering.DEDUPLICATED_AND_SORTED_ASCENDING) }; } final int size = Math.toIntExact(longHash.size()); diff --git a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/aggregation/blockhash/IntBlockHash.java b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/aggregation/blockhash/IntBlockHash.java index 4fcd9735f6158..08b2cd15aa53e 100644 --- a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/aggregation/blockhash/IntBlockHash.java +++ b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/aggregation/blockhash/IntBlockHash.java @@ -78,7 +78,7 @@ public IntBlock[] getKeys() { } BitSet nulls = new BitSet(1); nulls.set(0); - return new IntBlock[] { new IntArrayBlock(keys, keys.length, null, nulls, Block.MvOrdering.ASCENDING) }; + return new IntBlock[] { new IntArrayBlock(keys, keys.length, null, nulls, Block.MvOrdering.DEDUPLICATED_AND_SORTED_ASCENDING) }; } final int size = Math.toIntExact(longHash.size()); final int[] keys = new int[size]; diff --git a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/aggregation/blockhash/LongBlockHash.java b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/aggregation/blockhash/LongBlockHash.java index 5e5b46ae6eda1..00e93db9cec00 100644 --- a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/aggregation/blockhash/LongBlockHash.java +++ b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/aggregation/blockhash/LongBlockHash.java @@ -82,7 +82,8 @@ public LongBlock[] getKeys() { } BitSet nulls = new BitSet(1); nulls.set(0); - return new LongBlock[] { new LongArrayBlock(keys, keys.length, null, nulls, Block.MvOrdering.ASCENDING) }; + return new LongBlock[] { + new LongArrayBlock(keys, keys.length, null, nulls, Block.MvOrdering.DEDUPLICATED_AND_SORTED_ASCENDING) }; } final int size = Math.toIntExact(longHash.size()); diff --git a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/AbstractVectorBlock.java b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/AbstractVectorBlock.java index d83d26cf33831..4a019db5e03c0 100644 --- a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/AbstractVectorBlock.java +++ b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/AbstractVectorBlock.java @@ -52,7 +52,7 @@ public boolean mayHaveMultivaluedFields() { @Override public final MvOrdering mvOrdering() { - return MvOrdering.UNORDERED; + return MvOrdering.DEDUPLICATED_AND_SORTED_ASCENDING; } @Override diff --git a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/Block.java b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/Block.java index ca0721ebbe4f8..9c05d6d0ddfb8 100644 --- a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/Block.java +++ b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/Block.java @@ -102,12 +102,20 @@ public interface Block extends Accountable, NamedWriteable, Releasable { /** * How are multivalued fields ordered? - *

Note that there isn't a {@code DESCENDING} because we don't have - * anything that makes descending fields.

+ * Some operators can enable its optimization when mv_values are sorted ascending or de-duplicated. */ enum MvOrdering { - ASCENDING, - UNORDERED; + UNORDERED(false, false), + DEDUPLICATED_UNORDERD(true, false), + DEDUPLICATED_AND_SORTED_ASCENDING(true, true); + + private final boolean deduplicated; + private final boolean sortedAscending; + + MvOrdering(boolean deduplicated, boolean sortedAscending) { + this.deduplicated = deduplicated; + this.sortedAscending = sortedAscending; + } } /** @@ -115,6 +123,20 @@ enum MvOrdering { */ MvOrdering mvOrdering(); + /** + * Are multivalued fields de-duplicated in each position + */ + default boolean mvDeduplicated() { + return mayHaveMultivaluedFields() == false || mvOrdering().deduplicated; + } + + /** + * Are multivalued fields sorted ascending in each position + */ + default boolean mvSortedAscending() { + return mayHaveMultivaluedFields() == false || mvOrdering().sortedAscending; + } + /** * Expand multivalued fields into one row per value. Returns the * block if there aren't any multivalued fields to expand. @@ -172,7 +194,7 @@ interface Builder extends Releasable { /** * How are multivalued fields ordered? This defaults to {@link Block.MvOrdering#UNORDERED} - * but when you set it to {@link Block.MvOrdering#ASCENDING} some operators can optimize + * but when you set it to {@link Block.MvOrdering#DEDUPLICATED_AND_SORTED_ASCENDING} some operators can optimize * themselves. This is a promise that is never checked. If you set this * to anything other than {@link Block.MvOrdering#UNORDERED} be sure the values are in * that order or other operators will make mistakes. The actual ordering isn't checked diff --git a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/BlockUtils.java b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/BlockUtils.java index 0d09f7bb480e0..a41ea0383368d 100644 --- a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/BlockUtils.java +++ b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/data/BlockUtils.java @@ -8,10 +8,13 @@ package org.elasticsearch.compute.data; import org.apache.lucene.util.BytesRef; +import org.elasticsearch.common.Randomness; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashSet; import java.util.List; +import java.util.Random; import java.util.function.Consumer; import static org.elasticsearch.common.lucene.BytesRefs.toBytesRef; @@ -68,8 +71,13 @@ public static Block[] fromListRow(List row, int blockSize) { if (object instanceof List listVal) { BuilderWrapper wrapper = wrapperFor(fromJava(listVal.get(0).getClass()), blockSize); wrapper.accept(listVal); - if (isAscending(listVal)) { - wrapper.builder.mvOrdering(Block.MvOrdering.ASCENDING); + Random random = Randomness.get(); + if (isDeduplicated(listVal) && random.nextBoolean()) { + if (isAscending(listVal) && random.nextBoolean()) { + wrapper.builder.mvOrdering(Block.MvOrdering.DEDUPLICATED_AND_SORTED_ASCENDING); + } else { + wrapper.builder.mvOrdering(Block.MvOrdering.DEDUPLICATED_UNORDERD); + } } blocks[i] = wrapper.builder.build(); } else { @@ -100,6 +108,14 @@ private static boolean isAscending(List values) { return true; } + /** + * Detect blocks with deduplicated fields. This is *mostly* useful for + * exercising the specialized ascending implementations. + */ + private static boolean isDeduplicated(List values) { + return new HashSet<>(values).size() == values.size(); + } + public static Block[] fromList(List> list) { var size = list.size(); if (size == 0) { diff --git a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/lucene/BlockDocValuesReader.java b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/lucene/BlockDocValuesReader.java index 4290075b05ae8..28a9359497393 100644 --- a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/lucene/BlockDocValuesReader.java +++ b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/lucene/BlockDocValuesReader.java @@ -142,7 +142,7 @@ private static class LongSingletonValuesReader extends BlockDocValuesReader { @Override public LongBlock.Builder builder(int positionCount) { - return LongBlock.newBlockBuilder(positionCount).mvOrdering(Block.MvOrdering.ASCENDING); + return LongBlock.newBlockBuilder(positionCount).mvOrdering(Block.MvOrdering.DEDUPLICATED_AND_SORTED_ASCENDING); } @Override @@ -197,7 +197,7 @@ private static class LongValuesReader extends BlockDocValuesReader { @Override public LongBlock.Builder builder(int positionCount) { - return LongBlock.newBlockBuilder(positionCount).mvOrdering(Block.MvOrdering.ASCENDING); + return LongBlock.newBlockBuilder(positionCount).mvOrdering(Block.MvOrdering.DEDUPLICATED_AND_SORTED_ASCENDING); } @Override @@ -259,7 +259,7 @@ private static class IntSingletonValuesReader extends BlockDocValuesReader { @Override public IntBlock.Builder builder(int positionCount) { - return IntBlock.newBlockBuilder(positionCount).mvOrdering(Block.MvOrdering.ASCENDING); + return IntBlock.newBlockBuilder(positionCount).mvOrdering(Block.MvOrdering.DEDUPLICATED_AND_SORTED_ASCENDING); } @Override @@ -314,7 +314,7 @@ private static class IntValuesReader extends BlockDocValuesReader { @Override public IntBlock.Builder builder(int positionCount) { - return IntBlock.newBlockBuilder(positionCount).mvOrdering(Block.MvOrdering.ASCENDING); + return IntBlock.newBlockBuilder(positionCount).mvOrdering(Block.MvOrdering.DEDUPLICATED_AND_SORTED_ASCENDING); } @Override @@ -378,7 +378,7 @@ private static class DoubleSingletonValuesReader extends BlockDocValuesReader { @Override public DoubleBlock.Builder builder(int positionCount) { - return DoubleBlock.newBlockBuilder(positionCount).mvOrdering(Block.MvOrdering.ASCENDING); + return DoubleBlock.newBlockBuilder(positionCount).mvOrdering(Block.MvOrdering.DEDUPLICATED_AND_SORTED_ASCENDING); } @Override @@ -435,7 +435,7 @@ private static class DoubleValuesReader extends BlockDocValuesReader { @Override public DoubleBlock.Builder builder(int positionCount) { - return DoubleBlock.newBlockBuilder(positionCount).mvOrdering(Block.MvOrdering.ASCENDING); + return DoubleBlock.newBlockBuilder(positionCount).mvOrdering(Block.MvOrdering.DEDUPLICATED_AND_SORTED_ASCENDING); } @Override @@ -497,7 +497,7 @@ private static class BytesValuesReader extends BlockDocValuesReader { @Override public BytesRefBlock.Builder builder(int positionCount) { - return BytesRefBlock.newBlockBuilder(positionCount).mvOrdering(Block.MvOrdering.ASCENDING); + return BytesRefBlock.newBlockBuilder(positionCount).mvOrdering(Block.MvOrdering.DEDUPLICATED_AND_SORTED_ASCENDING); } @Override @@ -558,7 +558,7 @@ private static class BooleanSingletonValuesReader extends BlockDocValuesReader { @Override public BooleanBlock.Builder builder(int positionCount) { - return BooleanBlock.newBlockBuilder(positionCount).mvOrdering(Block.MvOrdering.ASCENDING); + return BooleanBlock.newBlockBuilder(positionCount).mvOrdering(Block.MvOrdering.DEDUPLICATED_AND_SORTED_ASCENDING); } @Override @@ -613,7 +613,7 @@ private static class BooleanValuesReader extends BlockDocValuesReader { @Override public BooleanBlock.Builder builder(int positionCount) { - return BooleanBlock.newBlockBuilder(positionCount).mvOrdering(Block.MvOrdering.ASCENDING); + return BooleanBlock.newBlockBuilder(positionCount).mvOrdering(Block.MvOrdering.DEDUPLICATED_AND_SORTED_ASCENDING); } @Override diff --git a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/operator/X-MultivalueDedupe.java.st b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/operator/X-MultivalueDedupe.java.st index 337b095ebe8d0..bd3e290a3625c 100644 --- a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/operator/X-MultivalueDedupe.java.st +++ b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/operator/X-MultivalueDedupe.java.st @@ -70,7 +70,7 @@ $endif$ * {@link Block} using an adaptive algorithm based on the size of the input list. */ public $Type$Block dedupeToBlockAdaptive() { - if (false == block.mayHaveMultivaluedFields()) { + if (block.mvDeduplicated()) { return block; } $Type$Block.Builder builder = $Type$Block.newBlockBuilder(block.getPositionCount()); @@ -122,7 +122,7 @@ $endif$ * which picks based on the number of elements at each position. */ public $Type$Block dedupeToBlockUsingCopyAndSort() { - if (false == block.mayHaveMultivaluedFields()) { + if (block.mvDeduplicated()) { return block; } $Type$Block.Builder builder = $Type$Block.newBlockBuilder(block.getPositionCount()); @@ -154,7 +154,7 @@ $endif$ * {@link #dedupeToBlockAdaptive} unless you need the results sorted. */ public $Type$Block dedupeToBlockUsingCopyMissing() { - if (false == block.mayHaveMultivaluedFields()) { + if (block.mvDeduplicated()) { return block; } $Type$Block.Builder builder = $Type$Block.newBlockBuilder(block.getPositionCount()); diff --git a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/operator/topn/X-KeyExtractor.java.st b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/operator/topn/X-KeyExtractor.java.st index 9ec03270da093..dbe0b23af93bb 100644 --- a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/operator/topn/X-KeyExtractor.java.st +++ b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/operator/topn/X-KeyExtractor.java.st @@ -10,7 +10,6 @@ package org.elasticsearch.compute.operator.topn; $if(BytesRef)$ import org.apache.lucene.util.BytesRef; $endif$ -import org.elasticsearch.compute.data.Block; import org.elasticsearch.compute.data.$Type$Block; import org.elasticsearch.compute.data.$Type$Vector; import org.elasticsearch.compute.operator.BreakingBytesRefBuilder; @@ -22,11 +21,11 @@ abstract class KeyExtractorFor$Type$ implements KeyExtractor { return new KeyExtractorFor$Type$.ForVector(encoder, nul, nonNul, v); } if (ascending) { - return block.mvOrdering() == Block.MvOrdering.ASCENDING + return block.mvSortedAscending() ? new KeyExtractorFor$Type$.MinForAscending(encoder, nul, nonNul, block) : new KeyExtractorFor$Type$.MinForUnordered(encoder, nul, nonNul, block); } - return block.mvOrdering() == Block.MvOrdering.ASCENDING + return block.mvSortedAscending() ? new KeyExtractorFor$Type$.MaxForAscending(encoder, nul, nonNul, block) : new KeyExtractorFor$Type$.MaxForUnordered(encoder, nul, nonNul, block); } diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/lucene/ValuesSourceReaderOperatorTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/lucene/ValuesSourceReaderOperatorTests.java index 4776b40e12115..4c0e33e5cfb82 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/lucene/ValuesSourceReaderOperatorTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/lucene/ValuesSourceReaderOperatorTests.java @@ -328,7 +328,7 @@ private void loadSimpleAndAssert(DriverContext driverContext, List input) assertThat(mvKeywords.getBytesRef(offset + v, new BytesRef()).utf8ToString(), equalTo(PREFIX[v] + key)); } if (key % 3 > 0) { - assertThat(mvKeywords.mvOrdering(), equalTo(Block.MvOrdering.ASCENDING)); + assertThat(mvKeywords.mvOrdering(), equalTo(Block.MvOrdering.DEDUPLICATED_AND_SORTED_ASCENDING)); } assertThat(bools.getBoolean(i), equalTo(key % 2 == 0)); @@ -338,7 +338,7 @@ private void loadSimpleAndAssert(DriverContext driverContext, List input) assertThat(mvBools.getBoolean(offset + v), equalTo(BOOLEANS[key % 3][v])); } if (key % 3 > 0) { - assertThat(mvBools.mvOrdering(), equalTo(Block.MvOrdering.ASCENDING)); + assertThat(mvBools.mvOrdering(), equalTo(Block.MvOrdering.DEDUPLICATED_AND_SORTED_ASCENDING)); } assertThat(mvInts.getValueCount(i), equalTo(key % 3 + 1)); @@ -347,7 +347,7 @@ private void loadSimpleAndAssert(DriverContext driverContext, List input) assertThat(mvInts.getInt(offset + v), equalTo(1_000 * key + v)); } if (key % 3 > 0) { - assertThat(mvInts.mvOrdering(), equalTo(Block.MvOrdering.ASCENDING)); + assertThat(mvInts.mvOrdering(), equalTo(Block.MvOrdering.DEDUPLICATED_AND_SORTED_ASCENDING)); } assertThat(mvLongs.getValueCount(i), equalTo(key % 3 + 1)); @@ -356,7 +356,7 @@ private void loadSimpleAndAssert(DriverContext driverContext, List input) assertThat(mvLongs.getLong(offset + v), equalTo(-1_000L * key + v)); } if (key % 3 > 0) { - assertThat(mvLongs.mvOrdering(), equalTo(Block.MvOrdering.ASCENDING)); + assertThat(mvLongs.mvOrdering(), equalTo(Block.MvOrdering.DEDUPLICATED_AND_SORTED_ASCENDING)); } assertThat(doubles.getDouble(i), equalTo(key / 123_456d)); @@ -365,7 +365,7 @@ private void loadSimpleAndAssert(DriverContext driverContext, List input) assertThat(mvDoubles.getDouble(offset + v), equalTo(key / 123_456d + v)); } if (key % 3 > 0) { - assertThat(mvDoubles.mvOrdering(), equalTo(Block.MvOrdering.ASCENDING)); + assertThat(mvDoubles.mvOrdering(), equalTo(Block.MvOrdering.DEDUPLICATED_AND_SORTED_ASCENDING)); } } } diff --git a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/topn/TopNOperatorTests.java b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/topn/TopNOperatorTests.java index 95f6613d3c0a4..c331b7ab013ae 100644 --- a/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/topn/TopNOperatorTests.java +++ b/x-pack/plugin/esql/compute/src/test/java/org/elasticsearch/compute/operator/topn/TopNOperatorTests.java @@ -1118,7 +1118,7 @@ public void testIPSortingUnorderedMultiValues() throws UnknownHostException { public void testIPSortingOrderedMultiValues() throws UnknownHostException { List> ips = new ArrayList<>(); - ips.add(List.of("123.4.245.23", "123.4.245.23")); + ips.add(List.of("123.4.245.23", "123.4.245.24")); ips.add(null); ips.add(List.of("104.30.244.2", "127.0.0.1")); ips.add(null); @@ -1135,17 +1135,17 @@ public void testIPSortingOrderedMultiValues() throws UnknownHostException { expectedDecodedIps.add(List.of("104.30.244.2", "127.0.0.1")); expectedDecodedIps.add(List.of("104.30.244.2", "124.255.255.255")); expectedDecodedIps.add(List.of("104.244.4.1")); - expectedDecodedIps.add(List.of("123.4.245.23", "123.4.245.23")); + expectedDecodedIps.add(List.of("123.4.245.23", "123.4.245.24")); } else { expectedDecodedIps.add(List.of("1.198.3.93", "2.3.4.5", "255.123.123.0")); expectedDecodedIps.add(List.of("104.30.244.2", "127.0.0.1")); expectedDecodedIps.add(List.of("104.30.244.2", "124.255.255.255")); - expectedDecodedIps.add(List.of("123.4.245.23", "123.4.245.23")); + expectedDecodedIps.add(List.of("123.4.245.23", "123.4.245.24")); expectedDecodedIps.add(List.of("104.244.4.1")); expectedDecodedIps.add(List.of("1.1.1.0", "32.183.93.40")); } - assertIPSortingOnMultiValues(ips, asc, Block.MvOrdering.ASCENDING, expectedDecodedIps); + assertIPSortingOnMultiValues(ips, asc, Block.MvOrdering.DEDUPLICATED_AND_SORTED_ASCENDING, expectedDecodedIps); } private void assertIPSortingOnMultiValues( diff --git a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMaxBooleanEvaluator.java b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMaxBooleanEvaluator.java index 3b15fe9f17293..67ad08a101dab 100644 --- a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMaxBooleanEvaluator.java +++ b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMaxBooleanEvaluator.java @@ -36,7 +36,7 @@ public String name() { */ @Override public Block evalNullable(Block fieldVal) { - if (fieldVal.mvOrdering() == Block.MvOrdering.ASCENDING) { + if (fieldVal.mvSortedAscending()) { return evalAscendingNullable(fieldVal); } BooleanBlock v = (BooleanBlock) fieldVal; @@ -66,7 +66,7 @@ public Block evalNullable(Block fieldVal) { */ @Override public Vector evalNotNullable(Block fieldVal) { - if (fieldVal.mvOrdering() == Block.MvOrdering.ASCENDING) { + if (fieldVal.mvSortedAscending()) { return evalAscendingNotNullable(fieldVal); } BooleanBlock v = (BooleanBlock) fieldVal; diff --git a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMaxBytesRefEvaluator.java b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMaxBytesRefEvaluator.java index 6401664c9aa0d..36eaecf36e345 100644 --- a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMaxBytesRefEvaluator.java +++ b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMaxBytesRefEvaluator.java @@ -37,7 +37,7 @@ public String name() { */ @Override public Block evalNullable(Block fieldVal) { - if (fieldVal.mvOrdering() == Block.MvOrdering.ASCENDING) { + if (fieldVal.mvSortedAscending()) { return evalAscendingNullable(fieldVal); } BytesRefBlock v = (BytesRefBlock) fieldVal; @@ -69,7 +69,7 @@ public Block evalNullable(Block fieldVal) { */ @Override public Vector evalNotNullable(Block fieldVal) { - if (fieldVal.mvOrdering() == Block.MvOrdering.ASCENDING) { + if (fieldVal.mvSortedAscending()) { return evalAscendingNotNullable(fieldVal); } BytesRefBlock v = (BytesRefBlock) fieldVal; diff --git a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMaxDoubleEvaluator.java b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMaxDoubleEvaluator.java index 0ec72b82e2438..072dc413168ec 100644 --- a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMaxDoubleEvaluator.java +++ b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMaxDoubleEvaluator.java @@ -35,7 +35,7 @@ public String name() { */ @Override public Block evalNullable(Block fieldVal) { - if (fieldVal.mvOrdering() == Block.MvOrdering.ASCENDING) { + if (fieldVal.mvSortedAscending()) { return evalAscendingNullable(fieldVal); } DoubleBlock v = (DoubleBlock) fieldVal; @@ -65,7 +65,7 @@ public Block evalNullable(Block fieldVal) { */ @Override public Vector evalNotNullable(Block fieldVal) { - if (fieldVal.mvOrdering() == Block.MvOrdering.ASCENDING) { + if (fieldVal.mvSortedAscending()) { return evalAscendingNotNullable(fieldVal); } DoubleBlock v = (DoubleBlock) fieldVal; diff --git a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMaxIntEvaluator.java b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMaxIntEvaluator.java index 2bf14b26c6c5e..4d6a68ed67f26 100644 --- a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMaxIntEvaluator.java +++ b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMaxIntEvaluator.java @@ -35,7 +35,7 @@ public String name() { */ @Override public Block evalNullable(Block fieldVal) { - if (fieldVal.mvOrdering() == Block.MvOrdering.ASCENDING) { + if (fieldVal.mvSortedAscending()) { return evalAscendingNullable(fieldVal); } IntBlock v = (IntBlock) fieldVal; @@ -65,7 +65,7 @@ public Block evalNullable(Block fieldVal) { */ @Override public Vector evalNotNullable(Block fieldVal) { - if (fieldVal.mvOrdering() == Block.MvOrdering.ASCENDING) { + if (fieldVal.mvSortedAscending()) { return evalAscendingNotNullable(fieldVal); } IntBlock v = (IntBlock) fieldVal; diff --git a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMaxLongEvaluator.java b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMaxLongEvaluator.java index ce5a95bee7699..fd0ee6bf57740 100644 --- a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMaxLongEvaluator.java +++ b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMaxLongEvaluator.java @@ -35,7 +35,7 @@ public String name() { */ @Override public Block evalNullable(Block fieldVal) { - if (fieldVal.mvOrdering() == Block.MvOrdering.ASCENDING) { + if (fieldVal.mvSortedAscending()) { return evalAscendingNullable(fieldVal); } LongBlock v = (LongBlock) fieldVal; @@ -65,7 +65,7 @@ public Block evalNullable(Block fieldVal) { */ @Override public Vector evalNotNullable(Block fieldVal) { - if (fieldVal.mvOrdering() == Block.MvOrdering.ASCENDING) { + if (fieldVal.mvSortedAscending()) { return evalAscendingNotNullable(fieldVal); } LongBlock v = (LongBlock) fieldVal; diff --git a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMedianIntEvaluator.java b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMedianIntEvaluator.java index 711992a20763e..a84a4059b1dc0 100644 --- a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMedianIntEvaluator.java +++ b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMedianIntEvaluator.java @@ -35,7 +35,7 @@ public String name() { */ @Override public Block evalNullable(Block fieldVal) { - if (fieldVal.mvOrdering() == Block.MvOrdering.ASCENDING) { + if (fieldVal.mvSortedAscending()) { return evalAscendingNullable(fieldVal); } IntBlock v = (IntBlock) fieldVal; @@ -65,7 +65,7 @@ public Block evalNullable(Block fieldVal) { */ @Override public Vector evalNotNullable(Block fieldVal) { - if (fieldVal.mvOrdering() == Block.MvOrdering.ASCENDING) { + if (fieldVal.mvSortedAscending()) { return evalAscendingNotNullable(fieldVal); } IntBlock v = (IntBlock) fieldVal; diff --git a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMedianLongEvaluator.java b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMedianLongEvaluator.java index 67d3c123a6953..4c5798bed2e35 100644 --- a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMedianLongEvaluator.java +++ b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMedianLongEvaluator.java @@ -36,7 +36,7 @@ public String name() { */ @Override public Block evalNullable(Block fieldVal) { - if (fieldVal.mvOrdering() == Block.MvOrdering.ASCENDING) { + if (fieldVal.mvSortedAscending()) { return evalAscendingNullable(fieldVal); } LongBlock v = (LongBlock) fieldVal; @@ -66,7 +66,7 @@ public Block evalNullable(Block fieldVal) { */ @Override public Vector evalNotNullable(Block fieldVal) { - if (fieldVal.mvOrdering() == Block.MvOrdering.ASCENDING) { + if (fieldVal.mvSortedAscending()) { return evalAscendingNotNullable(fieldVal); } LongBlock v = (LongBlock) fieldVal; diff --git a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMedianUnsignedLongEvaluator.java b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMedianUnsignedLongEvaluator.java index 93538708039b5..1731d0733b511 100644 --- a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMedianUnsignedLongEvaluator.java +++ b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMedianUnsignedLongEvaluator.java @@ -36,7 +36,7 @@ public String name() { */ @Override public Block evalNullable(Block fieldVal) { - if (fieldVal.mvOrdering() == Block.MvOrdering.ASCENDING) { + if (fieldVal.mvSortedAscending()) { return evalAscendingNullable(fieldVal); } LongBlock v = (LongBlock) fieldVal; @@ -66,7 +66,7 @@ public Block evalNullable(Block fieldVal) { */ @Override public Vector evalNotNullable(Block fieldVal) { - if (fieldVal.mvOrdering() == Block.MvOrdering.ASCENDING) { + if (fieldVal.mvSortedAscending()) { return evalAscendingNotNullable(fieldVal); } LongBlock v = (LongBlock) fieldVal; diff --git a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMinBooleanEvaluator.java b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMinBooleanEvaluator.java index 6e16c8db4b896..afb5e1cb7cda1 100644 --- a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMinBooleanEvaluator.java +++ b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMinBooleanEvaluator.java @@ -36,7 +36,7 @@ public String name() { */ @Override public Block evalNullable(Block fieldVal) { - if (fieldVal.mvOrdering() == Block.MvOrdering.ASCENDING) { + if (fieldVal.mvSortedAscending()) { return evalAscendingNullable(fieldVal); } BooleanBlock v = (BooleanBlock) fieldVal; @@ -66,7 +66,7 @@ public Block evalNullable(Block fieldVal) { */ @Override public Vector evalNotNullable(Block fieldVal) { - if (fieldVal.mvOrdering() == Block.MvOrdering.ASCENDING) { + if (fieldVal.mvSortedAscending()) { return evalAscendingNotNullable(fieldVal); } BooleanBlock v = (BooleanBlock) fieldVal; diff --git a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMinBytesRefEvaluator.java b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMinBytesRefEvaluator.java index 99a671cf0a2df..41b487553dc8e 100644 --- a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMinBytesRefEvaluator.java +++ b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMinBytesRefEvaluator.java @@ -37,7 +37,7 @@ public String name() { */ @Override public Block evalNullable(Block fieldVal) { - if (fieldVal.mvOrdering() == Block.MvOrdering.ASCENDING) { + if (fieldVal.mvSortedAscending()) { return evalAscendingNullable(fieldVal); } BytesRefBlock v = (BytesRefBlock) fieldVal; @@ -69,7 +69,7 @@ public Block evalNullable(Block fieldVal) { */ @Override public Vector evalNotNullable(Block fieldVal) { - if (fieldVal.mvOrdering() == Block.MvOrdering.ASCENDING) { + if (fieldVal.mvSortedAscending()) { return evalAscendingNotNullable(fieldVal); } BytesRefBlock v = (BytesRefBlock) fieldVal; diff --git a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMinDoubleEvaluator.java b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMinDoubleEvaluator.java index e40ff78d0d364..63da4bd86c673 100644 --- a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMinDoubleEvaluator.java +++ b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMinDoubleEvaluator.java @@ -35,7 +35,7 @@ public String name() { */ @Override public Block evalNullable(Block fieldVal) { - if (fieldVal.mvOrdering() == Block.MvOrdering.ASCENDING) { + if (fieldVal.mvSortedAscending()) { return evalAscendingNullable(fieldVal); } DoubleBlock v = (DoubleBlock) fieldVal; @@ -65,7 +65,7 @@ public Block evalNullable(Block fieldVal) { */ @Override public Vector evalNotNullable(Block fieldVal) { - if (fieldVal.mvOrdering() == Block.MvOrdering.ASCENDING) { + if (fieldVal.mvSortedAscending()) { return evalAscendingNotNullable(fieldVal); } DoubleBlock v = (DoubleBlock) fieldVal; diff --git a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMinIntEvaluator.java b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMinIntEvaluator.java index 9412930da53c5..46dedaed43a3d 100644 --- a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMinIntEvaluator.java +++ b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMinIntEvaluator.java @@ -35,7 +35,7 @@ public String name() { */ @Override public Block evalNullable(Block fieldVal) { - if (fieldVal.mvOrdering() == Block.MvOrdering.ASCENDING) { + if (fieldVal.mvSortedAscending()) { return evalAscendingNullable(fieldVal); } IntBlock v = (IntBlock) fieldVal; @@ -65,7 +65,7 @@ public Block evalNullable(Block fieldVal) { */ @Override public Vector evalNotNullable(Block fieldVal) { - if (fieldVal.mvOrdering() == Block.MvOrdering.ASCENDING) { + if (fieldVal.mvSortedAscending()) { return evalAscendingNotNullable(fieldVal); } IntBlock v = (IntBlock) fieldVal; diff --git a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMinLongEvaluator.java b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMinLongEvaluator.java index 1fac131f0de0c..8e17c8f08a906 100644 --- a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMinLongEvaluator.java +++ b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/MvMinLongEvaluator.java @@ -35,7 +35,7 @@ public String name() { */ @Override public Block evalNullable(Block fieldVal) { - if (fieldVal.mvOrdering() == Block.MvOrdering.ASCENDING) { + if (fieldVal.mvSortedAscending()) { return evalAscendingNullable(fieldVal); } LongBlock v = (LongBlock) fieldVal; @@ -65,7 +65,7 @@ public Block evalNullable(Block fieldVal) { */ @Override public Vector evalNotNullable(Block fieldVal) { - if (fieldVal.mvOrdering() == Block.MvOrdering.ASCENDING) { + if (fieldVal.mvSortedAscending()) { return evalAscendingNotNullable(fieldVal); } LongBlock v = (LongBlock) fieldVal; diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/AbstractMultivalueFunctionTestCase.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/AbstractMultivalueFunctionTestCase.java index 714112b2db543..cef65b6c477c5 100644 --- a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/AbstractMultivalueFunctionTestCase.java +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/expression/function/scalar/multivalue/AbstractMultivalueFunctionTestCase.java @@ -26,6 +26,8 @@ import java.math.BigInteger; import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedHashSet; import java.util.List; import java.util.function.BiFunction; import java.util.stream.DoubleStream; @@ -385,7 +387,17 @@ private static > void putInOrder(List mvData, Block.M switch (ordering) { case UNORDERED -> { } - case ASCENDING -> Collections.sort(mvData); + case DEDUPLICATED_UNORDERD -> { + var dedup = new LinkedHashSet<>(mvData); + mvData.clear(); + mvData.addAll(dedup); + } + case DEDUPLICATED_AND_SORTED_ASCENDING -> { + var dedup = new HashSet<>(mvData); + mvData.clear(); + mvData.addAll(dedup); + Collections.sort(mvData); + } default -> throw new UnsupportedOperationException("unsupported ordering [" + ordering + "]"); } } From 8f3d374d6dd58a220d5ee8f1f94c09d5d7e9dcb7 Mon Sep 17 00:00:00 2001 From: James Rodewig Date: Thu, 28 Sep 2023 16:05:12 -0400 Subject: [PATCH 22/32] [DOCS] Add security update to 8.9.2 release notes (#99949) Adds an update for the [audit logs security announcement](https://discuss.elastic.co/t/elasticsearch-8-9-2-and-7-17-13-security-update/342479) to the 8.9.2 release notes. This was overlooked when the announcement was first made. I plan to add a similar note to the 7.17.13 release notes. --- docs/reference/release-notes/8.9.2.asciidoc | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/docs/reference/release-notes/8.9.2.asciidoc b/docs/reference/release-notes/8.9.2.asciidoc index d4244eab27645..6b00405261daf 100644 --- a/docs/reference/release-notes/8.9.2.asciidoc +++ b/docs/reference/release-notes/8.9.2.asciidoc @@ -3,6 +3,25 @@ Also see <>. +[float] +[[security-updates-8.9.2]] +=== Security updates + +* {es} generally filters out sensitive information and credentials before +logging to the audit log. It was found that this filtering was not applied when +requests to {es} use certain deprecated `_xpack/security` URIs for APIs. The +impact of this flaw is that sensitive information, such as passwords and tokens, +might be printed in cleartext in {es} audit logs. Note that audit logging is +disabled by default and needs to be explicitly enabled. Even when audit logging +is enabled, request bodies that could contain sensitive information are not +printed to the audit log unless explicitly configured. ++ +The issue is resolved in {es} 8.9.2. ++ +For more information, see our related +https://discuss.elastic.co/t/elasticsearch-8-9-2-and-7-17-13-security-update/342479[security +announcement]. + [[bug-8.9.2]] [float] === Bug fixes From f99a80f42d4c5eff8a539ffca99a865e9e2dffe6 Mon Sep 17 00:00:00 2001 From: Chris Cressman Date: Thu, 28 Sep 2023 17:32:37 -0400 Subject: [PATCH 23/32] [DOCS] Create URLs for docs migrating from Enterprise Search (#100032) Several docs are going to migrate from Enterprise Search to Elasticsearch. Create the new URLs without yet placing the pages into the Elasticsearch navigation. This will allow us to handle redirects for Kibana, docs, and the web without waiting for additional content design decisions. The new URLs will be: - https://www.elastic.co/guide/en/elasticsearch/reference/master/ingest-pipeline-search.html - https://www.elastic.co/guide/en/elasticsearch/reference/master/ingest-pipeline-search-inference.html - https://www.elastic.co/guide/en/elasticsearch/reference/master/ingest-pipeline-search-inference.html#ingest-pipeline-search-inference-update-mapping - https://www.elastic.co/guide/en/elasticsearch/reference/master/nlp-example.html - https://www.elastic.co/guide/en/elasticsearch/reference/master/behavioral-analytics-overview.html - https://www.elastic.co/guide/en/elasticsearch/reference/master/behavioral-analytics-start.html - https://www.elastic.co/guide/en/elasticsearch/reference/master/behavioral-analytics-api.html - https://www.elastic.co/guide/en/elasticsearch/reference/master/behavioral-analytics-event.html - https://www.elastic.co/guide/en/elasticsearch/reference/master/behavioral-analytics-event-reference.html - https://www.elastic.co/guide/en/elasticsearch/reference/master/behavioral-analytics-cors.html - https://www.elastic.co/guide/en/elasticsearch/reference/master/search-application-overview.html - https://www.elastic.co/guide/en/elasticsearch/reference/master/search-application-api.html - https://www.elastic.co/guide/en/elasticsearch/reference/master/search-application-client.html - https://www.elastic.co/guide/en/elasticsearch/reference/master/search-application-security.html --- docs/reference/redirects.asciidoc | 70 +++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/docs/reference/redirects.asciidoc b/docs/reference/redirects.asciidoc index 8e1023c47b929..4ec8c203bbef9 100644 --- a/docs/reference/redirects.asciidoc +++ b/docs/reference/redirects.asciidoc @@ -1932,3 +1932,73 @@ Refer to <>. === Configure roles and users for remote clusters Refer to <>. + +[role="exclude",id="ingest-pipeline-search"] +=== Ingest pipelines for Search indices + +coming::[8.11.0] + +[role="exclude",id="ingest-pipeline-search-inference"] +=== Inference processing for Search indices + +coming::[8.11.0] + +[id="ingest-pipeline-search-inference-update-mapping"] +==== Update mapping + +coming::[8.11.0] + +[role="exclude",id="nlp-example"] +=== Tutorial: Natural language processing (NLP) + +coming::[8.11.0] + +[role="exclude",id="behavioral-analytics-overview"] +=== Elastic Behavioral Analytics + +coming::[8.11.0] + +[role="exclude",id="behavioral-analytics-start"] +=== Get started with Behavioral Analytics + +coming::[8.11.0] + +[role="exclude",id="behavioral-analytics-api"] +=== Behavioral Analytics APIs + +coming::[8.11.0] + +[role="exclude",id="behavioral-analytics-event"] +=== View Behavioral Analytics Events + +coming::[8.11.0] + +[role="exclude",id="behavioral-analytics-event-reference"] +=== Behavioral Analytics events reference + +coming::[8.11.0] + +[role="exclude",id="behavioral-analytics-cors"] +=== Set up CORS for Behavioral Analytics + +coming::[8.11.0] + +[role="exclude",id="search-application-overview"] +=== Elastic Search Applications + +coming::[8.11.0] + +[role="exclude",id="search-application-api"] +=== Search Applications search API and templates + +coming::[8.11.0] + +[role="exclude",id="search-application-client"] +=== Search Applications client + +coming::[8.11.0] + +[role="exclude",id="search-application-security"] +=== Search Applications security + +coming::[8.11.0] From f8d09e9c6cf4b611de3dc7509b0aab6e6a1d5d7a Mon Sep 17 00:00:00 2001 From: Stuart Tettemer Date: Thu, 28 Sep 2023 19:35:46 -0500 Subject: [PATCH 24/32] APM Metering API (#99832) Adds Metering instrument interfaces and adapter implementations for opentelemetry instrument types: * Gauge - a single number that can go up or down * Histogram - bucketed samples * Counter - monotonically increasing summed value * UpDownCounter - summed value that may decrease Supports both Long* and Double* versions of the instruments. Instruments can be registered and retrieved by name through APMMeter which is available via the APMTelemetryProvider. The metering provider starts as the open telemetry noop provider. `telemetry.metrics.enabled` turns on metering. --- docs/changelog/99832.yaml | 5 + .../org/elasticsearch/telemetry/apm/APM.java | 6 +- .../apm/internal/APMAgentSettings.java | 26 +- .../apm/internal/APMTelemetryProvider.java | 8 + .../apm/internal/metrics/APMMeter.java | 180 ++++++++++++++ .../internal/metrics/AbstractInstrument.java | 66 +++++ .../metrics/DoubleCounterAdapter.java | 52 ++++ .../internal/metrics/DoubleGaugeAdapter.java | 42 ++++ .../metrics/DoubleHistogramAdapter.java | 42 ++++ .../metrics/DoubleUpDownCounterAdapter.java | 46 ++++ .../apm/internal/metrics/Instruments.java | 184 ++++++++++++++ .../internal/metrics/LongCounterAdapter.java | 49 ++++ .../internal/metrics/LongGaugeAdapter.java | 46 ++++ .../metrics/LongHistogramAdapter.java | 46 ++++ .../metrics/LongUpDownCounterAdapter.java | 42 ++++ .../apm/internal/metrics/OtelHelper.java | 36 +++ .../apm/internal/tracing/APMTracer.java | 2 +- .../plugin-metadata/plugin-security.policy | 2 + .../apm/internal/metrics/APMMeterTests.java | 85 +++++++ .../metrics/InstrumentsConcurrencyTests.java | 112 +++++++++ .../internal/metrics/InstrumentsTests.java | 77 ++++++ server/src/main/java/module-info.java | 1 + .../telemetry/TelemetryProvider.java | 8 + .../telemetry/metric/DoubleCounter.java | 60 +++++ .../telemetry/metric/DoubleGauge.java | 47 ++++ .../telemetry/metric/DoubleHistogram.java | 48 ++++ .../telemetry/metric/DoubleUpDownCounter.java | 50 ++++ .../telemetry/metric/Instrument.java | 13 + .../telemetry/metric/LongCounter.java | 60 +++++ .../telemetry/metric/LongGauge.java | 49 ++++ .../telemetry/metric/LongHistogram.java | 48 ++++ .../telemetry/metric/LongUpDownCounter.java | 50 ++++ .../elasticsearch/telemetry/metric/Meter.java | 228 ++++++++++++++++++ 33 files changed, 1811 insertions(+), 5 deletions(-) create mode 100644 docs/changelog/99832.yaml create mode 100644 modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/APMMeter.java create mode 100644 modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/AbstractInstrument.java create mode 100644 modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/DoubleCounterAdapter.java create mode 100644 modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/DoubleGaugeAdapter.java create mode 100644 modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/DoubleHistogramAdapter.java create mode 100644 modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/DoubleUpDownCounterAdapter.java create mode 100644 modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/Instruments.java create mode 100644 modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/LongCounterAdapter.java create mode 100644 modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/LongGaugeAdapter.java create mode 100644 modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/LongHistogramAdapter.java create mode 100644 modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/LongUpDownCounterAdapter.java create mode 100644 modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/OtelHelper.java create mode 100644 modules/apm/src/test/java/org/elasticsearch/telemetry/apm/internal/metrics/APMMeterTests.java create mode 100644 modules/apm/src/test/java/org/elasticsearch/telemetry/apm/internal/metrics/InstrumentsConcurrencyTests.java create mode 100644 modules/apm/src/test/java/org/elasticsearch/telemetry/apm/internal/metrics/InstrumentsTests.java create mode 100644 server/src/main/java/org/elasticsearch/telemetry/metric/DoubleCounter.java create mode 100644 server/src/main/java/org/elasticsearch/telemetry/metric/DoubleGauge.java create mode 100644 server/src/main/java/org/elasticsearch/telemetry/metric/DoubleHistogram.java create mode 100644 server/src/main/java/org/elasticsearch/telemetry/metric/DoubleUpDownCounter.java create mode 100644 server/src/main/java/org/elasticsearch/telemetry/metric/Instrument.java create mode 100644 server/src/main/java/org/elasticsearch/telemetry/metric/LongCounter.java create mode 100644 server/src/main/java/org/elasticsearch/telemetry/metric/LongGauge.java create mode 100644 server/src/main/java/org/elasticsearch/telemetry/metric/LongHistogram.java create mode 100644 server/src/main/java/org/elasticsearch/telemetry/metric/LongUpDownCounter.java create mode 100644 server/src/main/java/org/elasticsearch/telemetry/metric/Meter.java diff --git a/docs/changelog/99832.yaml b/docs/changelog/99832.yaml new file mode 100644 index 0000000000000..9bd83591ba920 --- /dev/null +++ b/docs/changelog/99832.yaml @@ -0,0 +1,5 @@ +pr: 99832 +summary: APM Metering API +area: Infra/Core +type: enhancement +issues: [] diff --git a/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/APM.java b/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/APM.java index be59eda4a63c2..935c4958ba3d7 100644 --- a/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/APM.java +++ b/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/APM.java @@ -27,6 +27,7 @@ import org.elasticsearch.telemetry.TelemetryProvider; import org.elasticsearch.telemetry.apm.internal.APMAgentSettings; import org.elasticsearch.telemetry.apm.internal.APMTelemetryProvider; +import org.elasticsearch.telemetry.apm.internal.metrics.APMMeter; import org.elasticsearch.telemetry.apm.internal.tracing.APMTracer; import org.elasticsearch.threadpool.ThreadPool; import org.elasticsearch.watcher.ResourceWatcherService; @@ -97,13 +98,16 @@ public Collection createComponents( apmAgentSettings.syncAgentSystemProperties(settings); apmAgentSettings.addClusterSettingsListeners(clusterService, telemetryProvider.get()); - return List.of(apmTracer); + final APMMeter apmMeter = telemetryProvider.get().getMeter(); + + return List.of(apmTracer, apmMeter); } @Override public List> getSettings() { return List.of( APMAgentSettings.APM_ENABLED_SETTING, + APMAgentSettings.TELEMETRY_METRICS_ENABLED_SETTING, APMAgentSettings.APM_TRACING_NAMES_INCLUDE_SETTING, APMAgentSettings.APM_TRACING_NAMES_EXCLUDE_SETTING, APMAgentSettings.APM_TRACING_SANITIZE_FIELD_NAMES, diff --git a/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/APMAgentSettings.java b/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/APMAgentSettings.java index 75ca94bb13ad6..e4a194ebe0172 100644 --- a/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/APMAgentSettings.java +++ b/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/APMAgentSettings.java @@ -17,6 +17,7 @@ import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.core.SuppressForbidden; +import org.elasticsearch.telemetry.apm.internal.metrics.APMMeter; import org.elasticsearch.telemetry.apm.internal.tracing.APMTracer; import java.security.AccessController; @@ -40,14 +41,24 @@ public class APMAgentSettings { * Sensible defaults that Elasticsearch configures. This cannot be done via the APM agent * config file, as then their values could not be overridden dynamically via system properties. */ - static Map APM_AGENT_DEFAULT_SETTINGS = Map.of("transaction_sample_rate", "0.2"); + static Map APM_AGENT_DEFAULT_SETTINGS = Map.of( + "transaction_sample_rate", + "0.2", + "enable_experimental_instrumentations", + "true" + ); public void addClusterSettingsListeners(ClusterService clusterService, APMTelemetryProvider apmTelemetryProvider) { final ClusterSettings clusterSettings = clusterService.getClusterSettings(); final APMTracer apmTracer = apmTelemetryProvider.getTracer(); + final APMMeter apmMeter = apmTelemetryProvider.getMeter(); clusterSettings.addSettingsUpdateConsumer(APM_ENABLED_SETTING, enabled -> { apmTracer.setEnabled(enabled); + this.setAgentSetting("instrument", Boolean.toString(enabled)); + }); + clusterSettings.addSettingsUpdateConsumer(TELEMETRY_METRICS_ENABLED_SETTING, enabled -> { + apmMeter.setEnabled(enabled); // The agent records data other than spans, e.g. JVM metrics, so we toggle this setting in order to // minimise its impact to a running Elasticsearch. this.setAgentSetting("recording", Boolean.toString(enabled)); @@ -106,8 +117,10 @@ public void setAgentSetting(String key, String value) { private static final List PROHIBITED_AGENT_KEYS = List.of( // ES generates a config file and sets this value "config_file", - // ES controls this via `tracing.apm.enabled` - "recording" + // ES controls this via `telemetry.metrics.enabled` + "recording", + // ES controls this via `apm.enabled` + "instrument" ); public static final Setting.AffixSetting APM_AGENT_SETTINGS = Setting.prefixKeySetting( @@ -164,6 +177,13 @@ public void setAgentSetting(String key, String value) { NodeScope ); + public static final Setting TELEMETRY_METRICS_ENABLED_SETTING = Setting.boolSetting( + "telemetry.metrics.enabled", + false, + OperatorDynamic, + NodeScope + ); + public static final Setting APM_SECRET_TOKEN_SETTING = SecureSetting.secureString( APM_SETTING_PREFIX + "secret_token", null diff --git a/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/APMTelemetryProvider.java b/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/APMTelemetryProvider.java index 495afd43bf176..ae9d91cc6ec51 100644 --- a/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/APMTelemetryProvider.java +++ b/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/APMTelemetryProvider.java @@ -10,19 +10,27 @@ import org.elasticsearch.common.settings.Settings; import org.elasticsearch.telemetry.TelemetryProvider; +import org.elasticsearch.telemetry.apm.internal.metrics.APMMeter; import org.elasticsearch.telemetry.apm.internal.tracing.APMTracer; public class APMTelemetryProvider implements TelemetryProvider { private final Settings settings; private final APMTracer apmTracer; + private final APMMeter apmMeter; public APMTelemetryProvider(Settings settings) { this.settings = settings; apmTracer = new APMTracer(settings); + apmMeter = new APMMeter(settings); } @Override public APMTracer getTracer() { return apmTracer; } + + @Override + public APMMeter getMeter() { + return apmMeter; + } } diff --git a/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/APMMeter.java b/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/APMMeter.java new file mode 100644 index 0000000000000..0a8d425579ca2 --- /dev/null +++ b/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/APMMeter.java @@ -0,0 +1,180 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.telemetry.apm.internal.metrics; + +import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.metrics.Meter; + +import org.elasticsearch.cluster.service.ClusterService; +import org.elasticsearch.common.component.AbstractLifecycleComponent; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.telemetry.apm.internal.APMTelemetryProvider; +import org.elasticsearch.telemetry.metric.DoubleCounter; +import org.elasticsearch.telemetry.metric.DoubleGauge; +import org.elasticsearch.telemetry.metric.DoubleHistogram; +import org.elasticsearch.telemetry.metric.DoubleUpDownCounter; +import org.elasticsearch.telemetry.metric.LongCounter; +import org.elasticsearch.telemetry.metric.LongGauge; +import org.elasticsearch.telemetry.metric.LongHistogram; +import org.elasticsearch.telemetry.metric.LongUpDownCounter; + +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.function.Supplier; + +import static org.elasticsearch.telemetry.apm.internal.APMAgentSettings.TELEMETRY_METRICS_ENABLED_SETTING; + +public class APMMeter extends AbstractLifecycleComponent implements org.elasticsearch.telemetry.metric.Meter { + private final Instruments instruments; + + private final Supplier otelMeterSupplier; + private final Supplier noopMeterSupplier; + + private volatile boolean enabled; + + public APMMeter(Settings settings) { + this(settings, APMMeter.otelMeter(), APMMeter.noopMeter()); + } + + public APMMeter(Settings settings, Supplier otelMeterSupplier, Supplier noopMeterSupplier) { + this.enabled = TELEMETRY_METRICS_ENABLED_SETTING.get(settings); + this.otelMeterSupplier = otelMeterSupplier; + this.noopMeterSupplier = noopMeterSupplier; + this.instruments = new Instruments(enabled ? createOtelMeter() : createNoopMeter()); + } + + /** + * @see org.elasticsearch.telemetry.apm.internal.APMAgentSettings#addClusterSettingsListeners(ClusterService, APMTelemetryProvider) + */ + public void setEnabled(boolean enabled) { + this.enabled = enabled; + if (enabled) { + instruments.setProvider(createOtelMeter()); + } else { + instruments.setProvider(createNoopMeter()); + } + } + + @Override + protected void doStart() {} + + @Override + protected void doStop() { + instruments.setProvider(createNoopMeter()); + } + + @Override + protected void doClose() {} + + @Override + public DoubleCounter registerDoubleCounter(String name, String description, String unit) { + return instruments.registerDoubleCounter(name, description, unit); + } + + @Override + public DoubleCounter getDoubleCounter(String name) { + return instruments.getDoubleCounter(name); + } + + @Override + public DoubleUpDownCounter registerDoubleUpDownCounter(String name, String description, String unit) { + return instruments.registerDoubleUpDownCounter(name, description, unit); + } + + @Override + public DoubleUpDownCounter getDoubleUpDownCounter(String name) { + return instruments.getDoubleUpDownCounter(name); + } + + @Override + public DoubleGauge registerDoubleGauge(String name, String description, String unit) { + return instruments.registerDoubleGauge(name, description, unit); + } + + @Override + public DoubleGauge getDoubleGauge(String name) { + return instruments.getDoubleGauge(name); + } + + @Override + public DoubleHistogram registerDoubleHistogram(String name, String description, String unit) { + return instruments.registerDoubleHistogram(name, description, unit); + } + + @Override + public DoubleHistogram getDoubleHistogram(String name) { + return instruments.getDoubleHistogram(name); + } + + @Override + public LongCounter registerLongCounter(String name, String description, String unit) { + return instruments.registerLongCounter(name, description, unit); + } + + @Override + public LongCounter getLongCounter(String name) { + return instruments.getLongCounter(name); + } + + @Override + public LongUpDownCounter registerLongUpDownCounter(String name, String description, String unit) { + return instruments.registerLongUpDownCounter(name, description, unit); + } + + @Override + public LongUpDownCounter getLongUpDownCounter(String name) { + return instruments.getLongUpDownCounter(name); + } + + @Override + public LongGauge registerLongGauge(String name, String description, String unit) { + return instruments.registerLongGauge(name, description, unit); + } + + @Override + public LongGauge getLongGauge(String name) { + return instruments.getLongGauge(name); + } + + @Override + public LongHistogram registerLongHistogram(String name, String description, String unit) { + return instruments.registerLongHistogram(name, description, unit); + } + + @Override + public LongHistogram getLongHistogram(String name) { + return instruments.getLongHistogram(name); + } + + Meter createOtelMeter() { + assert this.enabled; + return AccessController.doPrivileged((PrivilegedAction) otelMeterSupplier::get); + } + + private Meter createNoopMeter() { + return noopMeterSupplier.get(); + } + + private static Supplier noopMeter() { + return () -> OpenTelemetry.noop().getMeter("noop"); + } + + // to be used within doPrivileged block + private static Supplier otelMeter() { + var openTelemetry = GlobalOpenTelemetry.get(); + var meter = openTelemetry.getMeter("elasticsearch"); + return () -> meter; + } + + // scope for testing + Instruments getInstruments() { + return instruments; + } +} diff --git a/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/AbstractInstrument.java b/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/AbstractInstrument.java new file mode 100644 index 0000000000000..d3d485f52bc49 --- /dev/null +++ b/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/AbstractInstrument.java @@ -0,0 +1,66 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.telemetry.apm.internal.metrics; + +import io.opentelemetry.api.metrics.Meter; + +import org.elasticsearch.core.Nullable; +import org.elasticsearch.telemetry.metric.Instrument; + +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicReference; + +/** + * An instrument that contains the name, description and unit. The delegate may be replaced when + * the provider is updated. + * Subclasses should implement the builder, which is used on initialization and provider updates. + * @param delegated instrument + */ +public abstract class AbstractInstrument implements Instrument { + private final AtomicReference delegate; + private final String name; + private final String description; + private final String unit; + + public AbstractInstrument(Meter meter, String name, String description, String unit) { + this.name = Objects.requireNonNull(name); + this.description = Objects.requireNonNull(description); + this.unit = Objects.requireNonNull(unit); + this.delegate = new AtomicReference<>(doBuildInstrument(meter)); + } + + private T doBuildInstrument(Meter meter) { + return AccessController.doPrivileged((PrivilegedAction) () -> buildInstrument(meter)); + } + + @Override + public String getName() { + return name; + } + + public String getUnit() { + return unit.toString(); + } + + T getInstrument() { + return delegate.get(); + } + + String getDescription() { + return description; + } + + void setProvider(@Nullable Meter meter) { + delegate.set(doBuildInstrument(Objects.requireNonNull(meter))); + } + + abstract T buildInstrument(Meter meter); +} diff --git a/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/DoubleCounterAdapter.java b/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/DoubleCounterAdapter.java new file mode 100644 index 0000000000000..b25ffdff5481b --- /dev/null +++ b/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/DoubleCounterAdapter.java @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.telemetry.apm.internal.metrics; + +import io.opentelemetry.api.metrics.Meter; + +import java.util.Map; +import java.util.Objects; + +/** + * DoubleGaugeAdapter wraps an otel ObservableDoubleMeasurement + */ +public class DoubleCounterAdapter extends AbstractInstrument + implements + org.elasticsearch.telemetry.metric.DoubleCounter { + + public DoubleCounterAdapter(Meter meter, String name, String description, String unit) { + super(meter, name, description, unit); + } + + io.opentelemetry.api.metrics.DoubleCounter buildInstrument(Meter meter) { + return Objects.requireNonNull(meter) + .counterBuilder(getName()) + .ofDoubles() + .setDescription(getDescription()) + .setUnit(getUnit()) + .build(); + } + + @Override + public void increment() { + getInstrument().add(1d); + } + + @Override + public void incrementBy(double inc) { + assert inc >= 0; + getInstrument().add(inc); + } + + @Override + public void incrementBy(double inc, Map attributes) { + assert inc >= 0; + getInstrument().add(inc, OtelHelper.fromMap(attributes)); + } +} diff --git a/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/DoubleGaugeAdapter.java b/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/DoubleGaugeAdapter.java new file mode 100644 index 0000000000000..9d55d475d4a93 --- /dev/null +++ b/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/DoubleGaugeAdapter.java @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.telemetry.apm.internal.metrics; + +import io.opentelemetry.api.metrics.Meter; + +import java.util.Map; +import java.util.Objects; + +/** + * DoubleGaugeAdapter wraps an otel ObservableDoubleMeasurement + */ +public class DoubleGaugeAdapter extends AbstractInstrument + implements + org.elasticsearch.telemetry.metric.DoubleGauge { + + public DoubleGaugeAdapter(Meter meter, String name, String description, String unit) { + super(meter, name, description, unit); + } + + @Override + io.opentelemetry.api.metrics.ObservableDoubleMeasurement buildInstrument(Meter meter) { + var builder = Objects.requireNonNull(meter).gaugeBuilder(getName()); + return builder.setDescription(getDescription()).setUnit(getUnit()).buildObserver(); + } + + @Override + public void record(double value) { + getInstrument().record(value); + } + + @Override + public void record(double value, Map attributes) { + getInstrument().record(value, OtelHelper.fromMap(attributes)); + } +} diff --git a/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/DoubleHistogramAdapter.java b/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/DoubleHistogramAdapter.java new file mode 100644 index 0000000000000..5fd1a8a189b0f --- /dev/null +++ b/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/DoubleHistogramAdapter.java @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.telemetry.apm.internal.metrics; + +import io.opentelemetry.api.metrics.Meter; + +import java.util.Map; +import java.util.Objects; + +/** + * DoubleHistogramAdapter wraps an otel DoubleHistogram + */ +public class DoubleHistogramAdapter extends AbstractInstrument + implements + org.elasticsearch.telemetry.metric.DoubleHistogram { + + public DoubleHistogramAdapter(Meter meter, String name, String description, String unit) { + super(meter, name, description, unit); + } + + @Override + io.opentelemetry.api.metrics.DoubleHistogram buildInstrument(Meter meter) { + var builder = Objects.requireNonNull(meter).histogramBuilder(getName()); + return builder.setDescription(getDescription()).setUnit(getUnit()).build(); + } + + @Override + public void record(double value) { + getInstrument().record(value); + } + + @Override + public void record(double value, Map attributes) { + getInstrument().record(value, OtelHelper.fromMap(attributes)); + } +} diff --git a/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/DoubleUpDownCounterAdapter.java b/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/DoubleUpDownCounterAdapter.java new file mode 100644 index 0000000000000..9a2fc1b564766 --- /dev/null +++ b/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/DoubleUpDownCounterAdapter.java @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.telemetry.apm.internal.metrics; + +import io.opentelemetry.api.metrics.Meter; + +import java.util.Map; +import java.util.Objects; + +/** + * DoubleUpDownCounterAdapter wraps an otel DoubleUpDownCounter + */ +public class DoubleUpDownCounterAdapter extends AbstractInstrument + implements + org.elasticsearch.telemetry.metric.DoubleUpDownCounter { + + public DoubleUpDownCounterAdapter(Meter meter, String name, String description, String unit) { + super(meter, name, description, unit); + } + + @Override + io.opentelemetry.api.metrics.DoubleUpDownCounter buildInstrument(Meter meter) { + return Objects.requireNonNull(meter) + .upDownCounterBuilder(getName()) + .ofDoubles() + .setDescription(getDescription()) + .setUnit(getUnit()) + .build(); + } + + @Override + public void add(double inc) { + getInstrument().add(inc); + } + + @Override + public void add(double inc, Map attributes) { + getInstrument().add(inc, OtelHelper.fromMap(attributes)); + } +} diff --git a/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/Instruments.java b/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/Instruments.java new file mode 100644 index 0000000000000..92d7d692f0ea5 --- /dev/null +++ b/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/Instruments.java @@ -0,0 +1,184 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.telemetry.apm.internal.metrics; + +import io.opentelemetry.api.metrics.Meter; + +import org.elasticsearch.common.util.concurrent.ConcurrentCollections; +import org.elasticsearch.common.util.concurrent.ReleasableLock; +import org.elasticsearch.telemetry.metric.DoubleCounter; +import org.elasticsearch.telemetry.metric.DoubleGauge; +import org.elasticsearch.telemetry.metric.DoubleHistogram; +import org.elasticsearch.telemetry.metric.DoubleUpDownCounter; +import org.elasticsearch.telemetry.metric.LongCounter; +import org.elasticsearch.telemetry.metric.LongGauge; +import org.elasticsearch.telemetry.metric.LongHistogram; +import org.elasticsearch.telemetry.metric.LongUpDownCounter; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.locks.ReentrantLock; + +/** + * Container for registering and fetching instruments by type and name. + * Instrument names must be unique for a given type on registration. + * {@link #setProvider(Meter)} is used to change the provider for all existing instruments. + */ +public class Instruments { + private final Registrar doubleCounters = new Registrar<>(); + private final Registrar doubleUpDownCounters = new Registrar<>(); + private final Registrar doubleGauges = new Registrar<>(); + private final Registrar doubleHistograms = new Registrar<>(); + private final Registrar longCounters = new Registrar<>(); + private final Registrar longUpDownCounters = new Registrar<>(); + private final Registrar longGauges = new Registrar<>(); + private final Registrar longHistograms = new Registrar<>(); + + private final Meter meter; + + public Instruments(Meter meter) { + this.meter = meter; + } + + private final List> registrars = List.of( + doubleCounters, + doubleUpDownCounters, + doubleGauges, + doubleHistograms, + longCounters, + longUpDownCounters, + longGauges, + longHistograms + ); + + // Access to registration has to be restricted when the provider is updated in ::setProvider + protected final ReleasableLock registerLock = new ReleasableLock(new ReentrantLock()); + + public DoubleCounter registerDoubleCounter(String name, String description, String unit) { + try (ReleasableLock lock = registerLock.acquire()) { + return doubleCounters.register(new DoubleCounterAdapter(meter, name, description, unit)); + } + } + + public DoubleCounter getDoubleCounter(String name) { + return doubleCounters.get(name); + } + + public DoubleUpDownCounter registerDoubleUpDownCounter(String name, String description, String unit) { + try (ReleasableLock lock = registerLock.acquire()) { + return doubleUpDownCounters.register(new DoubleUpDownCounterAdapter(meter, name, description, unit)); + } + } + + public DoubleUpDownCounter getDoubleUpDownCounter(String name) { + return doubleUpDownCounters.get(name); + } + + public DoubleGauge registerDoubleGauge(String name, String description, String unit) { + try (ReleasableLock lock = registerLock.acquire()) { + return doubleGauges.register(new DoubleGaugeAdapter(meter, name, description, unit)); + } + } + + public DoubleGauge getDoubleGauge(String name) { + return doubleGauges.get(name); + } + + public DoubleHistogram registerDoubleHistogram(String name, String description, String unit) { + try (ReleasableLock lock = registerLock.acquire()) { + return doubleHistograms.register(new DoubleHistogramAdapter(meter, name, description, unit)); + } + } + + public DoubleHistogram getDoubleHistogram(String name) { + return doubleHistograms.get(name); + } + + public LongCounter registerLongCounter(String name, String description, String unit) { + try (ReleasableLock lock = registerLock.acquire()) { + return longCounters.register(new LongCounterAdapter(meter, name, description, unit)); + } + } + + public LongCounter getLongCounter(String name) { + return longCounters.get(name); + } + + public LongUpDownCounter registerLongUpDownCounter(String name, String description, String unit) { + try (ReleasableLock lock = registerLock.acquire()) { + return longUpDownCounters.register(new LongUpDownCounterAdapter(meter, name, description, unit)); + } + } + + public LongUpDownCounter getLongUpDownCounter(String name) { + return longUpDownCounters.get(name); + } + + public LongGauge registerLongGauge(String name, String description, String unit) { + try (ReleasableLock lock = registerLock.acquire()) { + return longGauges.register(new LongGaugeAdapter(meter, name, description, unit)); + } + } + + public LongGauge getLongGauge(String name) { + return longGauges.get(name); + } + + public LongHistogram registerLongHistogram(String name, String description, String unit) { + try (ReleasableLock lock = registerLock.acquire()) { + return longHistograms.register(new LongHistogramAdapter(meter, name, description, unit)); + } + } + + public LongHistogram getLongHistogram(String name) { + return longHistograms.get(name); + } + + public void setProvider(Meter meter) { + try (ReleasableLock lock = registerLock.acquire()) { + for (Registrar registrar : registrars) { + registrar.setProvider(meter); + } + } + } + + /** + * A typed wrapper for a instrument that + * @param + */ + private static class Registrar> { + private final Map registered = ConcurrentCollections.newConcurrentMap(); + + T register(T instrument) { + registered.compute(instrument.getName(), (k, v) -> { + if (v != null) { + throw new IllegalStateException( + instrument.getClass().getSimpleName() + "[" + instrument.getName() + "] already registered" + ); + } + + return instrument; + }); + return instrument; + } + + T get(String name) { + return registered.get(name); + } + + void setProvider(Meter meter) { + registered.forEach((k, v) -> v.setProvider(meter)); + } + } + + // scope for testing + Meter getMeter() { + return meter; + } +} diff --git a/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/LongCounterAdapter.java b/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/LongCounterAdapter.java new file mode 100644 index 0000000000000..122d16d9e1aa4 --- /dev/null +++ b/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/LongCounterAdapter.java @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.telemetry.apm.internal.metrics; + +import io.opentelemetry.api.metrics.Meter; + +import java.util.Map; +import java.util.Objects; + +/** + * LongCounterAdapter wraps an otel LongCounter + */ +public class LongCounterAdapter extends AbstractInstrument + implements + org.elasticsearch.telemetry.metric.LongCounter { + + public LongCounterAdapter(Meter meter, String name, String description, String unit) { + super(meter, name, description, unit); + } + + @Override + io.opentelemetry.api.metrics.LongCounter buildInstrument(Meter meter) { + var builder = Objects.requireNonNull(meter).counterBuilder(getName()); + return builder.setDescription(getDescription()).setUnit(getUnit()).build(); + } + + @Override + public void increment() { + getInstrument().add(1L); + } + + @Override + public void incrementBy(long inc) { + assert inc >= 0; + getInstrument().add(inc); + } + + @Override + public void incrementBy(long inc, Map attributes) { + assert inc >= 0; + getInstrument().add(inc, OtelHelper.fromMap(attributes)); + } +} diff --git a/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/LongGaugeAdapter.java b/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/LongGaugeAdapter.java new file mode 100644 index 0000000000000..48430285a5173 --- /dev/null +++ b/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/LongGaugeAdapter.java @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.telemetry.apm.internal.metrics; + +import io.opentelemetry.api.metrics.Meter; + +import java.util.Map; +import java.util.Objects; + +/** + * LongGaugeAdapter wraps an otel ObservableLongMeasurement + */ +public class LongGaugeAdapter extends AbstractInstrument + implements + org.elasticsearch.telemetry.metric.LongGauge { + + public LongGaugeAdapter(Meter meter, String name, String description, String unit) { + super(meter, name, description, unit); + } + + @Override + io.opentelemetry.api.metrics.ObservableLongMeasurement buildInstrument(Meter meter) { + return Objects.requireNonNull(meter) + .gaugeBuilder(getName()) + .ofLongs() + .setDescription(getDescription()) + .setUnit(getUnit()) + .buildObserver(); + } + + @Override + public void record(long value) { + getInstrument().record(value); + } + + @Override + public void record(long value, Map attributes) { + getInstrument().record(value, OtelHelper.fromMap(attributes)); + } +} diff --git a/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/LongHistogramAdapter.java b/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/LongHistogramAdapter.java new file mode 100644 index 0000000000000..bb5be4866e7b7 --- /dev/null +++ b/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/LongHistogramAdapter.java @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.telemetry.apm.internal.metrics; + +import io.opentelemetry.api.metrics.Meter; + +import java.util.Map; +import java.util.Objects; + +/** + * LongHistogramAdapter wraps an otel LongHistogram + */ +public class LongHistogramAdapter extends AbstractInstrument + implements + org.elasticsearch.telemetry.metric.LongHistogram { + + public LongHistogramAdapter(Meter meter, String name, String description, String unit) { + super(meter, name, description, unit); + } + + @Override + io.opentelemetry.api.metrics.LongHistogram buildInstrument(Meter meter) { + return Objects.requireNonNull(meter) + .histogramBuilder(getName()) + .ofLongs() + .setDescription(getDescription()) + .setUnit(getUnit()) + .build(); + } + + @Override + public void record(long value) { + getInstrument().record(value); + } + + @Override + public void record(long value, Map attributes) { + getInstrument().record(value, OtelHelper.fromMap(attributes)); + } +} diff --git a/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/LongUpDownCounterAdapter.java b/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/LongUpDownCounterAdapter.java new file mode 100644 index 0000000000000..e5af85e4ed192 --- /dev/null +++ b/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/LongUpDownCounterAdapter.java @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.telemetry.apm.internal.metrics; + +import io.opentelemetry.api.metrics.Meter; + +import java.util.Map; +import java.util.Objects; + +/** + * LongUpDownCounterAdapter wraps an otel LongUpDownCounter + */ +public class LongUpDownCounterAdapter extends AbstractInstrument + implements + org.elasticsearch.telemetry.metric.LongUpDownCounter { + + public LongUpDownCounterAdapter(Meter meter, String name, String description, String unit) { + super(meter, name, description, unit); + } + + @Override + io.opentelemetry.api.metrics.LongUpDownCounter buildInstrument(Meter meter) { + var builder = Objects.requireNonNull(meter).upDownCounterBuilder(getName()); + return builder.setDescription(getDescription()).setUnit(getUnit()).build(); + } + + @Override + public void add(long inc) { + getInstrument().add(inc); + } + + @Override + public void add(long inc, Map attributes) { + getInstrument().add(inc, OtelHelper.fromMap(attributes)); + } +} diff --git a/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/OtelHelper.java b/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/OtelHelper.java new file mode 100644 index 0000000000000..673025a1a41f4 --- /dev/null +++ b/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/metrics/OtelHelper.java @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.telemetry.apm.internal.metrics; + +import io.opentelemetry.api.common.Attributes; + +import java.util.Map; + +class OtelHelper { + static Attributes fromMap(Map attributes) { + if (attributes == null || attributes.isEmpty()) { + return Attributes.empty(); + } + var builder = Attributes.builder(); + attributes.forEach((k, v) -> { + if (v instanceof String value) { + builder.put(k, value); + } else if (v instanceof Long value) { + builder.put(k, value); + } else if (v instanceof Double value) { + builder.put(k, value); + } else if (v instanceof Boolean value) { + builder.put(k, value); + } else { + throw new IllegalArgumentException("attributes do not support value type of [" + v.getClass().getCanonicalName() + "]"); + } + }); + return builder.build(); + } +} diff --git a/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/tracing/APMTracer.java b/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/tracing/APMTracer.java index daedb90047975..9f9fdb3dc26ef 100644 --- a/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/tracing/APMTracer.java +++ b/modules/apm/src/main/java/org/elasticsearch/telemetry/apm/internal/tracing/APMTracer.java @@ -150,7 +150,6 @@ APMServices createApmServices() { return AccessController.doPrivileged((PrivilegedAction) () -> { var openTelemetry = GlobalOpenTelemetry.get(); var tracer = openTelemetry.getTracer("elasticsearch", Version.CURRENT.toString()); - return new APMServices(tracer, openTelemetry); }); } @@ -452,4 +451,5 @@ private static Automaton patternsToAutomaton(List patterns) { } return Operations.union(automata); } + } diff --git a/modules/apm/src/main/plugin-metadata/plugin-security.policy b/modules/apm/src/main/plugin-metadata/plugin-security.policy index b85d3ec05c277..57da3a2efd301 100644 --- a/modules/apm/src/main/plugin-metadata/plugin-security.policy +++ b/modules/apm/src/main/plugin-metadata/plugin-security.policy @@ -11,6 +11,8 @@ grant { permission java.lang.RuntimePermission "createClassLoader"; permission java.lang.RuntimePermission "getClassLoader"; permission java.util.PropertyPermission "elastic.apm.*", "write"; + permission java.util.PropertyPermission "*", "read,write"; + permission java.lang.reflect.ReflectPermission "suppressAccessChecks"; }; grant codeBase "${codebase.elastic-apm-agent}" { diff --git a/modules/apm/src/test/java/org/elasticsearch/telemetry/apm/internal/metrics/APMMeterTests.java b/modules/apm/src/test/java/org/elasticsearch/telemetry/apm/internal/metrics/APMMeterTests.java new file mode 100644 index 0000000000000..1064b8820b089 --- /dev/null +++ b/modules/apm/src/test/java/org/elasticsearch/telemetry/apm/internal/metrics/APMMeterTests.java @@ -0,0 +1,85 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.telemetry.apm.internal.metrics; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.metrics.Meter; + +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.telemetry.apm.internal.APMAgentSettings; +import org.elasticsearch.telemetry.metric.DoubleCounter; +import org.elasticsearch.test.ESTestCase; + +import static org.hamcrest.Matchers.sameInstance; + +public class APMMeterTests extends ESTestCase { + Meter testOtel = OpenTelemetry.noop().getMeter("test"); + + Meter noopOtel = OpenTelemetry.noop().getMeter("noop"); + + public void testMeterIsSetUponConstruction() { + // test default + APMMeter apmMeter = new APMMeter(Settings.EMPTY, () -> testOtel, () -> noopOtel); + + Meter meter = apmMeter.getInstruments().getMeter(); + assertThat(meter, sameInstance(noopOtel)); + + // test explicitly enabled + var settings = Settings.builder().put(APMAgentSettings.TELEMETRY_METRICS_ENABLED_SETTING.getKey(), true).build(); + apmMeter = new APMMeter(settings, () -> testOtel, () -> noopOtel); + + meter = apmMeter.getInstruments().getMeter(); + assertThat(meter, sameInstance(testOtel)); + + // test explicitly disabled + settings = Settings.builder().put(APMAgentSettings.TELEMETRY_METRICS_ENABLED_SETTING.getKey(), true).build(); + apmMeter = new APMMeter(settings, () -> testOtel, () -> noopOtel); + + meter = apmMeter.getInstruments().getMeter(); + assertThat(meter, sameInstance(noopOtel)); + } + + public void testMeterIsOverridden() { + APMMeter apmMeter = new APMMeter(Settings.EMPTY, () -> testOtel, () -> noopOtel); + + Meter meter = apmMeter.getInstruments().getMeter(); + assertThat(meter, sameInstance(noopOtel)); + + apmMeter.setEnabled(true); + + meter = apmMeter.getInstruments().getMeter(); + assertThat(meter, sameInstance(testOtel)); + } + + public void testLookupByName() { + var settings = Settings.builder().put(APMAgentSettings.TELEMETRY_METRICS_ENABLED_SETTING.getKey(), true).build(); + + var apmMeter = new APMMeter(settings, () -> testOtel, () -> noopOtel); + + DoubleCounter registeredCounter = apmMeter.registerDoubleCounter("name", "desc", "unit"); + DoubleCounter lookedUpCounter = apmMeter.getDoubleCounter("name"); + + assertThat(lookedUpCounter, sameInstance(registeredCounter)); + } + + public void testNoopIsSetOnStop() { + var settings = Settings.builder().put(APMAgentSettings.TELEMETRY_METRICS_ENABLED_SETTING.getKey(), true).build(); + APMMeter apmMeter = new APMMeter(settings, () -> testOtel, () -> noopOtel); + apmMeter.start(); + + Meter meter = apmMeter.getInstruments().getMeter(); + assertThat(meter, sameInstance(testOtel)); + + apmMeter.stop(); + + meter = apmMeter.getInstruments().getMeter(); + assertThat(meter, sameInstance(noopOtel)); + } + +} diff --git a/modules/apm/src/test/java/org/elasticsearch/telemetry/apm/internal/metrics/InstrumentsConcurrencyTests.java b/modules/apm/src/test/java/org/elasticsearch/telemetry/apm/internal/metrics/InstrumentsConcurrencyTests.java new file mode 100644 index 0000000000000..51285894f27ee --- /dev/null +++ b/modules/apm/src/test/java/org/elasticsearch/telemetry/apm/internal/metrics/InstrumentsConcurrencyTests.java @@ -0,0 +1,112 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.telemetry.apm.internal.metrics; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.metrics.DoubleCounterBuilder; +import io.opentelemetry.api.metrics.DoubleGaugeBuilder; +import io.opentelemetry.api.metrics.DoubleHistogramBuilder; +import io.opentelemetry.api.metrics.LongCounter; +import io.opentelemetry.api.metrics.LongCounterBuilder; +import io.opentelemetry.api.metrics.LongUpDownCounterBuilder; +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.api.metrics.ObservableLongCounter; +import io.opentelemetry.api.metrics.ObservableLongMeasurement; + +import org.elasticsearch.test.ESTestCase; + +import java.util.concurrent.CountDownLatch; +import java.util.function.Consumer; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.sameInstance; + +public class InstrumentsConcurrencyTests extends ESTestCase { + String name = "name"; + String description = "desc"; + String unit = "kg"; + Meter noopMeter = OpenTelemetry.noop().getMeter("noop"); + CountDownLatch registerLatch = new CountDownLatch(1); + Meter lockingMeter = new Meter() { + @Override + public LongCounterBuilder counterBuilder(String name) { + return new LockingLongCounterBuilder(); + } + + @Override + public LongUpDownCounterBuilder upDownCounterBuilder(String name) { + return null; + } + + @Override + public DoubleHistogramBuilder histogramBuilder(String name) { + return null; + } + + @Override + public DoubleGaugeBuilder gaugeBuilder(String name) { + return null; + } + }; + + class LockingLongCounterBuilder implements LongCounterBuilder { + + @Override + public LongCounterBuilder setDescription(String description) { + return this; + } + + @Override + public LongCounterBuilder setUnit(String unit) { + return this; + } + + @Override + public DoubleCounterBuilder ofDoubles() { + return null; + } + + @Override + public LongCounter build() { + try { + registerLatch.await(); + } catch (Exception e) { + throw new RuntimeException(e); + } + return null; + } + + @Override + public ObservableLongCounter buildWithCallback(Consumer callback) { + return null; + } + } + + public void testLockingWhenRegistering() throws Exception { + Instruments instruments = new Instruments(lockingMeter); + + var registerThread = new Thread(() -> instruments.registerLongCounter(name, description, unit)); + // registerThread has a countDown latch that is simulating a long-running registration + registerThread.start(); + var setProviderThread = new Thread(() -> instruments.setProvider(noopMeter)); + // a setProviderThread will attempt to override a meter, but will wait to acquireLock + setProviderThread.start(); + + // assert that a thread is waiting for a lock during long-running registration + assertBusy(() -> assertThat(setProviderThread.getState(), equalTo(Thread.State.WAITING))); + // assert that the old lockingMeter is still in place + assertBusy(() -> assertThat(instruments.getMeter(), sameInstance(lockingMeter))); + + // finish long-running registration + registerLatch.countDown(); + // assert that a meter was overriden + assertBusy(() -> assertThat(instruments.getMeter(), sameInstance(lockingMeter))); + + } +} diff --git a/modules/apm/src/test/java/org/elasticsearch/telemetry/apm/internal/metrics/InstrumentsTests.java b/modules/apm/src/test/java/org/elasticsearch/telemetry/apm/internal/metrics/InstrumentsTests.java new file mode 100644 index 0000000000000..daf511fcf7042 --- /dev/null +++ b/modules/apm/src/test/java/org/elasticsearch/telemetry/apm/internal/metrics/InstrumentsTests.java @@ -0,0 +1,77 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.telemetry.apm.internal.metrics; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.metrics.Meter; + +import org.elasticsearch.test.ESTestCase; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.sameInstance; + +public class InstrumentsTests extends ESTestCase { + Meter noopMeter = OpenTelemetry.noop().getMeter("noop"); + Meter someOtherMeter = OpenTelemetry.noop().getMeter("xyz"); + String name = "name"; + String description = "desc"; + String unit = "kg"; + + public void testRegistrationAndLookup() { + Instruments instruments = new Instruments(noopMeter); + { + var registered = instruments.registerDoubleCounter(name, description, unit); + var lookedUp = instruments.getDoubleCounter(name); + assertThat(registered, sameInstance(lookedUp)); + } + { + var registered = instruments.registerDoubleUpDownCounter(name, description, unit); + var lookedUp = instruments.getDoubleUpDownCounter(name); + assertThat(registered, sameInstance(lookedUp)); + } + { + var registered = instruments.registerDoubleGauge(name, description, unit); + var lookedUp = instruments.getDoubleGauge(name); + assertThat(registered, sameInstance(lookedUp)); + } + { + var registered = instruments.registerDoubleHistogram(name, description, unit); + var lookedUp = instruments.getDoubleHistogram(name); + assertThat(registered, sameInstance(lookedUp)); + } + { + var registered = instruments.registerLongCounter(name, description, unit); + var lookedUp = instruments.getLongCounter(name); + assertThat(registered, sameInstance(lookedUp)); + } + { + var registered = instruments.registerLongUpDownCounter(name, description, unit); + var lookedUp = instruments.getLongUpDownCounter(name); + assertThat(registered, sameInstance(lookedUp)); + } + { + var registered = instruments.registerLongGauge(name, description, unit); + var lookedUp = instruments.getLongGauge(name); + assertThat(registered, sameInstance(lookedUp)); + } + { + var registered = instruments.registerLongHistogram(name, description, unit); + var lookedUp = instruments.getLongHistogram(name); + assertThat(registered, sameInstance(lookedUp)); + } + } + + public void testNameValidation() { + Instruments instruments = new Instruments(noopMeter); + + instruments.registerLongHistogram(name, description, unit); + var e = expectThrows(IllegalStateException.class, () -> instruments.registerLongHistogram(name, description, unit)); + assertThat(e.getMessage(), equalTo("LongHistogramAdapter[name] already registered")); + } +} diff --git a/server/src/main/java/module-info.java b/server/src/main/java/module-info.java index 99ce5910c9775..1a082e7558577 100644 --- a/server/src/main/java/module-info.java +++ b/server/src/main/java/module-info.java @@ -385,6 +385,7 @@ org.elasticsearch.serverless.apifiltering; exports org.elasticsearch.telemetry.tracing; exports org.elasticsearch.telemetry; + exports org.elasticsearch.telemetry.metric; provides java.util.spi.CalendarDataProvider with org.elasticsearch.common.time.IsoCalendarDataProvider; provides org.elasticsearch.xcontent.ErrorOnUnknown with org.elasticsearch.common.xcontent.SuggestingErrorOnUnknown; diff --git a/server/src/main/java/org/elasticsearch/telemetry/TelemetryProvider.java b/server/src/main/java/org/elasticsearch/telemetry/TelemetryProvider.java index 0df8aeedac7f8..add994787227f 100644 --- a/server/src/main/java/org/elasticsearch/telemetry/TelemetryProvider.java +++ b/server/src/main/java/org/elasticsearch/telemetry/TelemetryProvider.java @@ -8,11 +8,15 @@ package org.elasticsearch.telemetry; +import org.elasticsearch.telemetry.metric.Meter; import org.elasticsearch.telemetry.tracing.Tracer; public interface TelemetryProvider { + Tracer getTracer(); + Meter getMeter(); + TelemetryProvider NOOP = new TelemetryProvider() { @Override @@ -20,5 +24,9 @@ public Tracer getTracer() { return Tracer.NOOP; } + @Override + public Meter getMeter() { + return Meter.NOOP; + } }; } diff --git a/server/src/main/java/org/elasticsearch/telemetry/metric/DoubleCounter.java b/server/src/main/java/org/elasticsearch/telemetry/metric/DoubleCounter.java new file mode 100644 index 0000000000000..c98701bb0a1bb --- /dev/null +++ b/server/src/main/java/org/elasticsearch/telemetry/metric/DoubleCounter.java @@ -0,0 +1,60 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.telemetry.metric; + +import java.util.Map; + +/** + * A monotonically increasing metric that uses a double. + * Useful for capturing the number of bytes received, number of requests, etc. + */ +public interface DoubleCounter extends Instrument { + /** + * Add one to the current counter. + */ + void increment(); + + /** + * Increment the counter. + * @param inc amount to increment, non-negative + */ + void incrementBy(double inc); + + /** + * Increment the counter. + * @param inc amount to increment, non-negative + * @param attributes key-value pairs to associate with this increment + */ + void incrementBy(double inc, Map attributes); + + /** + * Noop counter for use in tests. + */ + DoubleCounter NOOP = new DoubleCounter() { + @Override + public String getName() { + return "noop"; + } + + @Override + public void increment() { + + } + + @Override + public void incrementBy(double inc) { + + } + + @Override + public void incrementBy(double inc, Map attributes) { + + } + }; +} diff --git a/server/src/main/java/org/elasticsearch/telemetry/metric/DoubleGauge.java b/server/src/main/java/org/elasticsearch/telemetry/metric/DoubleGauge.java new file mode 100644 index 0000000000000..797c125900bb8 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/telemetry/metric/DoubleGauge.java @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.telemetry.metric; + +import java.util.Map; + +/** + * Record non-additive double values. eg number of running threads, current load + */ +public interface DoubleGauge extends Instrument { + /** + * Record the current value for measured item + */ + void record(double value); + + /** + * Record the current value + * @param attributes key-value pairs to associate with the current measurement + */ + void record(double value, Map attributes); + + /** + * Noop gauge for tests + */ + DoubleGauge NOOP = new DoubleGauge() { + @Override + public String getName() { + return "noop"; + } + + @Override + public void record(double value) { + + } + + @Override + public void record(double value, Map attributes) { + + } + }; +} diff --git a/server/src/main/java/org/elasticsearch/telemetry/metric/DoubleHistogram.java b/server/src/main/java/org/elasticsearch/telemetry/metric/DoubleHistogram.java new file mode 100644 index 0000000000000..11958ea36cd3d --- /dev/null +++ b/server/src/main/java/org/elasticsearch/telemetry/metric/DoubleHistogram.java @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.telemetry.metric; + +import java.util.Map; + +/** + * Record arbitrary values that are summarized statistically, useful for percentiles and histograms. + */ +public interface DoubleHistogram extends Instrument { + /** + * Record a sample for the measured item + * @param value + */ + void record(double value); + + /** + * Record a sample for the measured item + * @param attributes key-value pairs to associate with the current sample + */ + void record(double value, Map attributes); + + /** + * Noop histogram for tests + */ + DoubleHistogram NOOP = new DoubleHistogram() { + @Override + public String getName() { + return "noop"; + } + + @Override + public void record(double value) { + + } + + @Override + public void record(double value, Map attributes) { + + } + }; +} diff --git a/server/src/main/java/org/elasticsearch/telemetry/metric/DoubleUpDownCounter.java b/server/src/main/java/org/elasticsearch/telemetry/metric/DoubleUpDownCounter.java new file mode 100644 index 0000000000000..7d484ebf07d32 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/telemetry/metric/DoubleUpDownCounter.java @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.telemetry.metric; + +import java.util.Map; + +/** + * A counter that supports decreasing and increasing values. + * Useful for capturing the number of requests in a queue. + */ +public interface DoubleUpDownCounter extends Instrument { + /** + * Add to the counter + * @param inc may be negative. + */ + void add(double inc); + + /** + * Add to the counter + * @param inc may be negative. + * @param attributes key-value pairs to associate with this increment + */ + void add(double inc, Map attributes); + + /** + * Noop counter for use in tests + */ + DoubleUpDownCounter NOOP = new DoubleUpDownCounter() { + @Override + public String getName() { + return "noop"; + } + + @Override + public void add(double inc) { + + } + + @Override + public void add(double inc, Map attributes) { + + } + }; +} diff --git a/server/src/main/java/org/elasticsearch/telemetry/metric/Instrument.java b/server/src/main/java/org/elasticsearch/telemetry/metric/Instrument.java new file mode 100644 index 0000000000000..19a7e259120f2 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/telemetry/metric/Instrument.java @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.telemetry.metric; + +public interface Instrument { + String getName(); +} diff --git a/server/src/main/java/org/elasticsearch/telemetry/metric/LongCounter.java b/server/src/main/java/org/elasticsearch/telemetry/metric/LongCounter.java new file mode 100644 index 0000000000000..f8f2150163835 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/telemetry/metric/LongCounter.java @@ -0,0 +1,60 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.telemetry.metric; + +import java.util.Map; + +/** + * A monotonically increasing metric that uses a long. Useful for integral values such as the number of bytes received, + * number of requests, etc. + */ +public interface LongCounter extends Instrument { + /** + * Add one to the current counter + */ + void increment(); + + /** + * Increment the counter + * @param inc amount to increment + */ + void incrementBy(long inc); + + /** + * Increment the counter. + * @param inc amount to increment + * @param attributes key-value pairs to associate with this increment + */ + void incrementBy(long inc, Map attributes); + + /** + * Noop counter for use in tests. + */ + LongCounter NOOP = new LongCounter() { + @Override + public String getName() { + return "noop"; + } + + @Override + public void increment() { + + } + + @Override + public void incrementBy(long inc) { + + } + + @Override + public void incrementBy(long inc, Map attributes) { + + } + }; +} diff --git a/server/src/main/java/org/elasticsearch/telemetry/metric/LongGauge.java b/server/src/main/java/org/elasticsearch/telemetry/metric/LongGauge.java new file mode 100644 index 0000000000000..71539064ce53e --- /dev/null +++ b/server/src/main/java/org/elasticsearch/telemetry/metric/LongGauge.java @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.telemetry.metric; + +import java.util.Map; + +/** + * Record non-additive long values. + */ +public interface LongGauge extends Instrument { + + /** + * Record the current value of the measured item. + * @param value + */ + void record(long value); + + /** + * Record the current value + * @param attributes key-value pairs to associate with the current measurement + */ + void record(long value, Map attributes); + + /** + * Noop gauge for tests + */ + LongGauge NOOP = new LongGauge() { + @Override + public String getName() { + return "noop"; + } + + @Override + public void record(long value) { + + } + + @Override + public void record(long value, Map attributes) { + + } + }; +} diff --git a/server/src/main/java/org/elasticsearch/telemetry/metric/LongHistogram.java b/server/src/main/java/org/elasticsearch/telemetry/metric/LongHistogram.java new file mode 100644 index 0000000000000..27d5261f755ef --- /dev/null +++ b/server/src/main/java/org/elasticsearch/telemetry/metric/LongHistogram.java @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.telemetry.metric; + +import java.util.Map; + +/** + * Record arbitrary values that are summarized statistically, useful for percentiles and histograms. + */ +public interface LongHistogram extends Instrument { + /** + * Record a sample for the measured item + * @param value + */ + void record(long value); + + /** + * Record a sample for the measured item + * @param attributes key-value pairs to associate with the current sample + */ + void record(long value, Map attributes); + + /** + * Noop histogram for tests + */ + LongHistogram NOOP = new LongHistogram() { + @Override + public String getName() { + return "noop"; + } + + @Override + public void record(long value) { + + } + + @Override + public void record(long value, Map attributes) { + + } + }; +} diff --git a/server/src/main/java/org/elasticsearch/telemetry/metric/LongUpDownCounter.java b/server/src/main/java/org/elasticsearch/telemetry/metric/LongUpDownCounter.java new file mode 100644 index 0000000000000..f62030da8f6bd --- /dev/null +++ b/server/src/main/java/org/elasticsearch/telemetry/metric/LongUpDownCounter.java @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.telemetry.metric; + +import java.util.Map; + +/** + * A counter that supports decreasing and increasing values. + * Useful for capturing the number of requests in a queue. + */ +public interface LongUpDownCounter extends Instrument { + /** + * Add to the counter + * @param inc may be negative. + */ + void add(long inc); + + /** + * Add to the counter + * @param inc may be negative. + * @param attributes key-value pairs to associate with this increment + */ + void add(long inc, Map attributes); + + /** + * Noop counter for use in tests + */ + LongUpDownCounter NOOP = new LongUpDownCounter() { + @Override + public String getName() { + return "noop"; + } + + @Override + public void add(long inc) { + + } + + @Override + public void add(long inc, Map attributes) { + + } + }; +} diff --git a/server/src/main/java/org/elasticsearch/telemetry/metric/Meter.java b/server/src/main/java/org/elasticsearch/telemetry/metric/Meter.java new file mode 100644 index 0000000000000..77bbf6f673fd3 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/telemetry/metric/Meter.java @@ -0,0 +1,228 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.telemetry.metric; + +/** + * Container for metering instruments. Meters with the same name and type (DoubleCounter, etc) can + * only be registered once. + * TODO(stu): describe name, unit and description + */ +public interface Meter { + /** + * Register a {@link DoubleCounter}. The returned object may be reused. + * @param name name of the counter + * @param description description of purpose + * @param unit the unit (bytes, sec, hour) + * @return the registered meter. + */ + DoubleCounter registerDoubleCounter(String name, String description, String unit); + + /** + * Retrieved a previously registered {@link DoubleCounter}. + * @param name name of the counter + * @return the registered meter. + */ + DoubleCounter getDoubleCounter(String name); + + /** + * Register a {@link DoubleUpDownCounter}. The returned object may be reused. + * @param name name of the counter + * @param description description of purpose + * @param unit the unit (bytes, sec, hour) + * @return the registered meter. + */ + DoubleUpDownCounter registerDoubleUpDownCounter(String name, String description, String unit); + + /** + * Retrieved a previously registered {@link DoubleUpDownCounter}. + * @param name name of the counter + * @return the registered meter. + */ + DoubleUpDownCounter getDoubleUpDownCounter(String name); + + /** + * Register a {@link DoubleGauge}. The returned object may be reused. + * @param name name of the gauge + * @param description description of purpose + * @param unit the unit (bytes, sec, hour) + * @return the registered meter. + */ + DoubleGauge registerDoubleGauge(String name, String description, String unit); + + /** + * Retrieved a previously registered {@link DoubleGauge}. + * @param name name of the gauge + * @return the registered meter. + */ + DoubleGauge getDoubleGauge(String name); + + /** + * Register a {@link DoubleHistogram}. The returned object may be reused. + * @param name name of the histogram + * @param description description of purpose + * @param unit the unit (bytes, sec, hour) + * @return the registered meter. + */ + DoubleHistogram registerDoubleHistogram(String name, String description, String unit); + + /** + * Retrieved a previously registered {@link DoubleHistogram}. + * @param name name of the histogram + * @return the registered meter. + */ + DoubleHistogram getDoubleHistogram(String name); + + /** + * Register a {@link LongCounter}. The returned object may be reused. + * @param name name of the counter + * @param description description of purpose + * @param unit the unit (bytes, sec, hour) + * @return the registered meter. + */ + LongCounter registerLongCounter(String name, String description, String unit); + + /** + * Retrieved a previously registered {@link LongCounter}. + * @param name name of the counter + * @return the registered meter. + */ + LongCounter getLongCounter(String name); + + /** + * Register a {@link LongUpDownCounter}. The returned object may be reused. + * @param name name of the counter + * @param description description of purpose + * @param unit the unit (bytes, sec, hour) + * @return the registered meter. + */ + LongUpDownCounter registerLongUpDownCounter(String name, String description, String unit); + + /** + * Retrieved a previously registered {@link LongUpDownCounter}. + * @param name name of the counter + * @return the registered meter. + */ + LongUpDownCounter getLongUpDownCounter(String name); + + /** + * Register a {@link LongGauge}. The returned object may be reused. + * @param name name of the gauge + * @param description description of purpose + * @param unit the unit (bytes, sec, hour) + * @return the registered meter. + */ + LongGauge registerLongGauge(String name, String description, String unit); + + /** + * Retrieved a previously registered {@link LongGauge}. + * @param name name of the gauge + * @return the registered meter. + */ + LongGauge getLongGauge(String name); + + /** + * Register a {@link LongHistogram}. The returned object may be reused. + * @param name name of the histogram + * @param description description of purpose + * @param unit the unit (bytes, sec, hour) + * @return the registered meter. + */ + LongHistogram registerLongHistogram(String name, String description, String unit); + + /** + * Retrieved a previously registered {@link LongHistogram}. + * @param name name of the histogram + * @return the registered meter. + */ + LongHistogram getLongHistogram(String name); + + /** + * Noop implementation for tests + */ + Meter NOOP = new Meter() { + @Override + public DoubleCounter registerDoubleCounter(String name, String description, String unit) { + return DoubleCounter.NOOP; + } + + @Override + public DoubleCounter getDoubleCounter(String name) { + return DoubleCounter.NOOP; + } + + public DoubleUpDownCounter registerDoubleUpDownCounter(String name, String description, String unit) { + return DoubleUpDownCounter.NOOP; + } + + @Override + public DoubleUpDownCounter getDoubleUpDownCounter(String name) { + return DoubleUpDownCounter.NOOP; + } + + @Override + public DoubleGauge registerDoubleGauge(String name, String description, String unit) { + return DoubleGauge.NOOP; + } + + @Override + public DoubleGauge getDoubleGauge(String name) { + return DoubleGauge.NOOP; + } + + @Override + public DoubleHistogram registerDoubleHistogram(String name, String description, String unit) { + return DoubleHistogram.NOOP; + } + + @Override + public DoubleHistogram getDoubleHistogram(String name) { + return DoubleHistogram.NOOP; + } + + @Override + public LongCounter registerLongCounter(String name, String description, String unit) { + return LongCounter.NOOP; + } + + @Override + public LongCounter getLongCounter(String name) { + return LongCounter.NOOP; + } + + @Override + public LongUpDownCounter registerLongUpDownCounter(String name, String description, String unit) { + return LongUpDownCounter.NOOP; + } + + @Override + public LongUpDownCounter getLongUpDownCounter(String name) { + return LongUpDownCounter.NOOP; + } + + @Override + public LongGauge registerLongGauge(String name, String description, String unit) { + return LongGauge.NOOP; + } + + @Override + public LongGauge getLongGauge(String name) { + return LongGauge.NOOP; + } + + @Override + public LongHistogram registerLongHistogram(String name, String description, String unit) { + return LongHistogram.NOOP; + } + + @Override + public LongHistogram getLongHistogram(String name) { + return LongHistogram.NOOP; + } + }; +} From 44a2d68f197094dc3af8d053ff166e0228256a71 Mon Sep 17 00:00:00 2001 From: Nhat Nguyen Date: Thu, 28 Sep 2023 17:45:17 -0700 Subject: [PATCH 25/32] Replace recursive with loop in PackedValuesBlockHash (#99992) This change replaces the recursion with a loop when packing and hashing multiple keys. While the recursive version is clever, it may not be as straightforward for future readers. Using a loop also helps us avoid StackOverflow when grouping by a large number of keys. --- .../blockhash/PackedValuesBlockHash.java | 192 +++++++++--------- 1 file changed, 95 insertions(+), 97 deletions(-) diff --git a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/aggregation/blockhash/PackedValuesBlockHash.java b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/aggregation/blockhash/PackedValuesBlockHash.java index 31f65e9b70053..7ecaddf2092fa 100644 --- a/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/aggregation/blockhash/PackedValuesBlockHash.java +++ b/x-pack/plugin/esql/compute/src/main/java/org/elasticsearch/compute/aggregation/blockhash/PackedValuesBlockHash.java @@ -22,8 +22,6 @@ import org.elasticsearch.compute.operator.BatchEncoder; import org.elasticsearch.compute.operator.HashAggregationOperator; import org.elasticsearch.compute.operator.MultivalueDedupe; -import org.elasticsearch.logging.LogManager; -import org.elasticsearch.logging.Logger; import java.util.Arrays; import java.util.List; @@ -51,19 +49,20 @@ * } */ final class PackedValuesBlockHash extends BlockHash { - private static final Logger logger = LogManager.getLogger(PackedValuesBlockHash.class); static final int DEFAULT_BATCH_SIZE = Math.toIntExact(ByteSizeValue.ofKb(10).getBytes()); - private final List groups; private final int emitBatchSize; private final BytesRefHash bytesRefHash; private final int nullTrackingBytes; + private final BytesRef scratch = new BytesRef(); + private final BytesRefBuilder bytes = new BytesRefBuilder(); + private final Group[] groups; - PackedValuesBlockHash(List groups, BigArrays bigArrays, int emitBatchSize) { - this.groups = groups; + PackedValuesBlockHash(List specs, BigArrays bigArrays, int emitBatchSize) { + this.groups = specs.stream().map(Group::new).toArray(Group[]::new); this.emitBatchSize = emitBatchSize; this.bytesRefHash = new BytesRefHash(1, bigArrays); - this.nullTrackingBytes = groups.size() / 8 + 1; + this.nullTrackingBytes = (groups.length + 7) / 8; } @Override @@ -75,23 +74,28 @@ void add(Page page, GroupingAggregatorFunction.AddInput addInput, int batchSize) new AddWork(page, addInput, batchSize).add(); } + private static class Group { + final HashAggregationOperator.GroupSpec spec; + BatchEncoder encoder; + int positionOffset; + int valueOffset; + int loopedIndex; + int valueCount; + int bytesStart; + + Group(HashAggregationOperator.GroupSpec spec) { + this.spec = spec; + } + } + class AddWork extends LongLongBlockHash.AbstractAddBlock { - final BatchEncoder[] encoders = new BatchEncoder[groups.size()]; - final int[] positionOffsets = new int[groups.size()]; - final int[] valueOffsets = new int[groups.size()]; - final BytesRef[] scratches = new BytesRef[groups.size()]; - final BytesRefBuilder bytes = new BytesRefBuilder(); final int positionCount; - int position; - int count; - int bufferedGroup; AddWork(Page page, GroupingAggregatorFunction.AddInput addInput, int batchSize) { super(emitBatchSize, addInput); - for (int g = 0; g < groups.size(); g++) { - encoders[g] = MultivalueDedupe.batchEncoder(page.getBlock(groups.get(g).channel()), batchSize); - scratches[g] = new BytesRef(); + for (Group group : groups) { + group.encoder = MultivalueDedupe.batchEncoder(page.getBlock(group.spec.channel()), batchSize); } bytes.grow(nullTrackingBytes); this.positionCount = page.getPositionCount(); @@ -104,91 +108,86 @@ class AddWork extends LongLongBlockHash.AbstractAddBlock { */ void add() { for (position = 0; position < positionCount; position++) { - if (logger.isTraceEnabled()) { - logger.trace("position {}", position); - } // Make sure all encoders have encoded the current position and the offsets are queued to it's start - for (int g = 0; g < encoders.length; g++) { - positionOffsets[g]++; - while (positionOffsets[g] >= encoders[g].positionCount()) { - encoders[g].encodeNextBatch(); - positionOffsets[g] = 0; - valueOffsets[g] = 0; + boolean singleEntry = true; + for (Group g : groups) { + var encoder = g.encoder; + g.positionOffset++; + while (g.positionOffset >= encoder.positionCount()) { + encoder.encodeNextBatch(); + g.positionOffset = 0; + g.valueOffset = 0; } + g.valueCount = encoder.valueCount(g.positionOffset); + singleEntry &= (g.valueCount == 1); } - - count = 0; Arrays.fill(bytes.bytes(), 0, nullTrackingBytes, (byte) 0); bytes.setLength(nullTrackingBytes); - addPosition(0); - switch (count) { - case 0 -> throw new IllegalStateException("didn't find any values"); - case 1 -> { - ords.appendInt(bufferedGroup); - addedValue(position); - } - default -> ords.endPositionEntry(); - } - for (int g = 0; g < encoders.length; g++) { - valueOffsets[g] += encoders[g].valueCount(positionOffsets[g]); + if (singleEntry) { + addSingleEntry(); + } else { + addMultipleEntries(); } } emitOrds(); } - private void addPosition(int g) { - if (g == groups.size()) { - addBytes(); - return; - } - int start = bytes.length(); - int count = encoders[g].valueCount(positionOffsets[g]); - assert count > 0; - int valueOffset = valueOffsets[g]; - BytesRef v = encoders[g].read(valueOffset++, scratches[g]); - if (logger.isTraceEnabled()) { - logger.trace("\t".repeat(g + 1) + v); - } - if (v.length == 0) { - assert count == 1 : "null value in non-singleton list"; - int nullByte = g / 8; - int nullShift = g % 8; - bytes.bytes()[nullByte] |= (byte) (1 << nullShift); - } - bytes.setLength(start); - bytes.append(v); - addPosition(g + 1); // TODO stack overflow protection - for (int i = 1; i < count; i++) { - v = encoders[g].read(valueOffset++, scratches[g]); - if (logger.isTraceEnabled()) { - logger.trace("\t".repeat(g + 1) + v); + private void addSingleEntry() { + for (int g = 0; g < groups.length; g++) { + Group group = groups[g]; + BytesRef v = group.encoder.read(group.valueOffset++, scratch); + if (v.length == 0) { + int nullByte = g / 8; + int nullShift = g % 8; + bytes.bytes()[nullByte] |= (byte) (1 << nullShift); + } else { + bytes.append(v); } - assert v.length > 0 : "null value after the first position"; - bytes.setLength(start); - bytes.append(v); - addPosition(g + 1); } + int ord = Math.toIntExact(hashOrdToGroup(bytesRefHash.add(bytes.get()))); + ords.appendInt(ord); + addedValue(position); } - private void addBytes() { - int group = Math.toIntExact(hashOrdToGroup(bytesRefHash.add(bytes.get()))); - switch (count) { - case 0 -> bufferedGroup = group; - case 1 -> { - ords.beginPositionEntry(); - ords.appendInt(bufferedGroup); - addedValueInMultivaluePosition(position); - ords.appendInt(group); - addedValueInMultivaluePosition(position); + private void addMultipleEntries() { + ords.beginPositionEntry(); + int g = 0; + outer: for (;;) { + for (; g < groups.length; g++) { + Group group = groups[g]; + group.bytesStart = bytes.length(); + BytesRef v = group.encoder.read(group.valueOffset + group.loopedIndex, scratch); + ++group.loopedIndex; + if (v.length == 0) { + assert group.valueCount == 1 : "null value in non-singleton list"; + int nullByte = g / 8; + int nullShift = g % 8; + bytes.bytes()[nullByte] |= (byte) (1 << nullShift); + } else { + bytes.append(v); + } } - default -> { - ords.appendInt(group); - addedValueInMultivaluePosition(position); + // emit ords + int ord = Math.toIntExact(hashOrdToGroup(bytesRefHash.add(bytes.get()))); + ords.appendInt(ord); + addedValueInMultivaluePosition(position); + + // rewind + Group group = groups[--g]; + bytes.setLength(group.bytesStart); + while (group.loopedIndex == group.valueCount) { + group.loopedIndex = 0; + if (g == 0) { + break outer; + } else { + group = groups[--g]; + bytes.setLength(group.bytesStart); + } } } - count++; - if (logger.isTraceEnabled()) { - logger.trace("{} = {}", bytes.get(), group); + ords.endPositionEntry(); + for (Group group : groups) { + group.valueOffset += group.valueCount; } } } @@ -196,16 +195,16 @@ private void addBytes() { @Override public Block[] getKeys() { int size = Math.toIntExact(bytesRefHash.size()); - BatchEncoder.Decoder[] decoders = new BatchEncoder.Decoder[groups.size()]; - Block.Builder[] builders = new Block.Builder[groups.size()]; + BatchEncoder.Decoder[] decoders = new BatchEncoder.Decoder[groups.length]; + Block.Builder[] builders = new Block.Builder[groups.length]; for (int g = 0; g < builders.length; g++) { - ElementType elementType = groups.get(g).elementType(); + ElementType elementType = groups[g].spec.elementType(); decoders[g] = BatchEncoder.decoder(elementType); builders[g] = elementType.newBlockBuilder(size); } - BytesRef values[] = new BytesRef[(int) Math.min(100, bytesRefHash.size())]; - BytesRef nulls[] = new BytesRef[values.length]; + BytesRef[] values = new BytesRef[(int) Math.min(100, bytesRefHash.size())]; + BytesRef[] nulls = new BytesRef[values.length]; for (int offset = 0; offset < values.length; offset++) { values[offset] = new BytesRef(); nulls[offset] = new BytesRef(); @@ -231,7 +230,7 @@ public Block[] getKeys() { readKeys(decoders, builders, nulls, values, offset); } - Block[] keyBlocks = new Block[groups.size()]; + Block[] keyBlocks = new Block[groups.length]; for (int g = 0; g < keyBlocks.length; g++) { keyBlocks[g] = builders[g].build(); } @@ -271,13 +270,12 @@ public String toString() { StringBuilder b = new StringBuilder(); b.append("PackedValuesBlockHash{groups=["); boolean first = true; - for (HashAggregationOperator.GroupSpec spec : groups) { - if (first) { - first = false; - } else { + for (int i = 0; i < groups.length; i++) { + if (i > 0) { b.append(", "); } - b.append(spec.channel()).append(':').append(spec.elementType()); + Group group = groups[i]; + b.append(group.spec.channel()).append(':').append(group.spec.elementType()); } b.append("], entries=").append(bytesRefHash.size()); b.append(", size=").append(ByteSizeValue.ofBytes(bytesRefHash.ramBytesUsed())); From 05189ef395e4d28c5fbc5612c7f27954648680bd Mon Sep 17 00:00:00 2001 From: Albert Zaharovits Date: Fri, 29 Sep 2023 08:22:20 +0300 Subject: [PATCH 26/32] Debug log OIDC ID token validation exception (#100002) This ensures we debug log OIDC ID token validation exceptions, which should help admins with troubleshooting the OIDC realm setup. --- .../authc/jwt/JwtSignatureValidator.java | 25 +++++++------------ .../xpack/security/authc/jwt/JwtUtil.java | 23 +++++++++++++++++ .../oidc/OpenIdConnectAuthenticator.java | 9 +++++-- 3 files changed, 39 insertions(+), 18 deletions(-) diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/jwt/JwtSignatureValidator.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/jwt/JwtSignatureValidator.java index 91224a8246169..b1ee1b77998ec 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/jwt/JwtSignatureValidator.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/jwt/JwtSignatureValidator.java @@ -17,7 +17,6 @@ import com.nimbusds.jose.jwk.JWK; import com.nimbusds.jose.jwk.OctetSequenceKey; import com.nimbusds.jose.jwk.RSAKey; -import com.nimbusds.jose.util.Base64URL; import com.nimbusds.jwt.SignedJWT; import org.apache.logging.log4j.LogManager; @@ -40,9 +39,10 @@ import java.util.Arrays; import java.util.List; -import java.util.function.Supplier; import java.util.stream.Stream; +import static org.elasticsearch.xpack.security.authc.jwt.JwtUtil.toStringRedactSignature; + public interface JwtSignatureValidator extends Releasable { Logger logger = LogManager.getLogger(JwtSignatureValidator.class); @@ -361,7 +361,7 @@ default void validateSignature(final SignedJWT jwt, final List jwks) throws final String id = jwt.getHeader().getKeyID(); final JWSAlgorithm alg = jwt.getHeader().getAlgorithm(); - tracer.append("Filtering [{}] possible JWKs to verifying signature for JWT [{}].", jwks.size(), getSafePrintableJWT(jwt)); + tracer.append("Filtering [{}] possible JWKs to verifying signature for JWT [{}].", jwks.size(), toStringRedactSignature(jwt)); // If JWT has optional kid header, and realm JWKs have optional kid attribute, any mismatches JWT.kid vs JWK.kid can be ignored. // Keep any JWKs if JWK optional kid attribute is missing. Keep all JWKs if JWT optional kid header is missing. @@ -399,7 +399,11 @@ default void validateSignature(final SignedJWT jwt, final List jwks) throws int attempt = 0; int maxAttempts = jwksConfigured.size(); - tracer.append("Attempting to verify signature for JWT [{}] against [{}] possible JWKs.", getSafePrintableJWT(jwt), maxAttempts); + tracer.append( + "Attempting to verify signature for JWT [{}] against [{}] possible JWKs.", + toStringRedactSignature(jwt), + maxAttempts + ); for (final JWK jwk : jwksConfigured) { attempt++; if (jwt.verify(createJwsVerifier(jwk))) { @@ -429,7 +433,7 @@ default void validateSignature(final SignedJWT jwt, final List jwks) throws ); } } - throw new ElasticsearchException("JWT [" + getSafePrintableJWT(jwt).get() + "] signature verification failed."); + throw new ElasticsearchException("JWT [" + toStringRedactSignature(jwt).get() + "] signature verification failed."); } } @@ -458,15 +462,4 @@ interface PkcJwkSetReloadNotifier { void reloaded(); } - /** - * @param jwt The signed JWT - * @return A print safe supplier to describe a JWT that redacts the signature. While the signature is not generally sensitive, - * we don't want to leak the entire JWT to the log to avoid a possible replay. - */ - private Supplier getSafePrintableJWT(SignedJWT jwt) { - Base64URL[] parts = jwt.getParsedParts(); - assert parts.length == 3; - return () -> parts[0].toString() + "." + parts[1].toString() + "."; - } - } diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/jwt/JwtUtil.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/jwt/JwtUtil.java index 3e3533f028b38..9168c5c0925bd 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/jwt/JwtUtil.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/jwt/JwtUtil.java @@ -7,9 +7,12 @@ package org.elasticsearch.xpack.security.authc.jwt; +import com.nimbusds.jose.JWSObject; import com.nimbusds.jose.jwk.JWK; import com.nimbusds.jose.jwk.JWKSet; +import com.nimbusds.jose.util.Base64URL; import com.nimbusds.jose.util.JSONObjectUtils; +import com.nimbusds.jwt.JWT; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; @@ -59,6 +62,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import java.util.Objects; import java.util.function.Supplier; import javax.net.ssl.HostnameVerifier; @@ -389,4 +393,23 @@ public void close() { closed = true; } } + + /** + * @param jwt The signed JWT + * @return A print safe supplier to describe a JWT that redacts the signature. While the signature is not generally sensitive, + * we don't want to leak the entire JWT to the log to avoid a possible replay. + */ + public static Supplier toStringRedactSignature(JWT jwt) { + if (jwt instanceof JWSObject) { + Base64URL[] parts = jwt.getParsedParts(); + assert parts.length == 3; + assert parts[0] != null; + assert parts[1] != null; + assert parts[2] != null; + assert Objects.equals(parts[2], ((JWSObject) jwt).getSignature()); + return () -> parts[0] + "." + parts[1] + "."; + } else { + return jwt::getParsedString; + } + } } diff --git a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/oidc/OpenIdConnectAuthenticator.java b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/oidc/OpenIdConnectAuthenticator.java index 73bc36c94e2d5..754d2a82dd835 100644 --- a/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/oidc/OpenIdConnectAuthenticator.java +++ b/x-pack/plugin/security/src/main/java/org/elasticsearch/xpack/security/authc/oidc/OpenIdConnectAuthenticator.java @@ -93,6 +93,7 @@ import org.elasticsearch.xpack.core.security.authc.RealmSettings; import org.elasticsearch.xpack.core.security.authc.oidc.OpenIdConnectRealmSettings; import org.elasticsearch.xpack.core.ssl.SSLService; +import org.elasticsearch.xpack.security.authc.jwt.JwtUtil; import java.io.IOException; import java.net.URI; @@ -293,14 +294,18 @@ void getUserClaims( .triggerReload(ActionListener.wrap(v -> { getUserClaims(accessToken, idToken, expectedNonce, false, claimsListener); }, ex -> { - LOGGER.trace("Attempted and failed to refresh JWK cache upon token validation failure", e); + LOGGER.debug("Attempted and failed to refresh JWK cache upon token validation failure", e); claimsListener.onFailure(ex); })); } else { + LOGGER.debug("Failed to parse or validate the ID Token", e); claimsListener.onFailure(new ElasticsearchSecurityException("Failed to parse or validate the ID Token", e)); } } catch (com.nimbusds.oauth2.sdk.ParseException | ParseException | JOSEException e) { - LOGGER.debug("ID Token: [{}], Nonce: [{}]", idToken.getParsedString(), expectedNonce); + LOGGER.debug( + () -> format("ID Token: [%s], Nonce: [%s]", JwtUtil.toStringRedactSignature(idToken).get(), expectedNonce.toString()), + e + ); claimsListener.onFailure(new ElasticsearchSecurityException("Failed to parse or validate the ID Token", e)); } } From fc962ff32ba0f48ae539def65e0f44a12c0dfec4 Mon Sep 17 00:00:00 2001 From: Matteo Piergiovanni <134913285+piergm@users.noreply.github.com> Date: Fri, 29 Sep 2023 08:23:57 +0200 Subject: [PATCH 27/32] [CI] SearchResponseTests#testSerialization failing resolved (#100020) Removed class variables running, partial and failed that are now calculated when needed. Having them caused issues due to the fact that they always reflected the content of the clusterInfo map that could be updated in various part of the code and the update would not reflect on those counters. Updated class methods equals and hashCode accordingly. Unmuted test related to issue --- docs/changelog/100020.yaml | 6 ++ .../action/search/SearchResponse.java | 63 +++++++++---------- .../action/search/SearchResponseTests.java | 1 - 3 files changed, 37 insertions(+), 33 deletions(-) create mode 100644 docs/changelog/100020.yaml diff --git a/docs/changelog/100020.yaml b/docs/changelog/100020.yaml new file mode 100644 index 0000000000000..9f97778860eef --- /dev/null +++ b/docs/changelog/100020.yaml @@ -0,0 +1,6 @@ +pr: 100020 +summary: "[CI] `SearchResponseTests#testSerialization` failing resolved" +area: Search +type: bug +issues: + - 100005 diff --git a/server/src/main/java/org/elasticsearch/action/search/SearchResponse.java b/server/src/main/java/org/elasticsearch/action/search/SearchResponse.java index f9f5f43494711..ba785fd4d9637 100644 --- a/server/src/main/java/org/elasticsearch/action/search/SearchResponse.java +++ b/server/src/main/java/org/elasticsearch/action/search/SearchResponse.java @@ -471,9 +471,6 @@ public static class Clusters implements ToXContentFragment, Writeable { private final int total; private final int successful; // not used for minimize_roundtrips=true; dynamically determined from clusterInfo map private final int skipped; // not used for minimize_roundtrips=true; dynamically determined from clusterInfo map - private final int running; // not used for minimize_roundtrips=true; dynamically determined from clusterInfo map - private final int partial; // not used for minimize_roundtrips=true; dynamically determined from clusterInfo map - private final int failed; // not used for minimize_roundtrips=true; dynamically determined from clusterInfo map // key to map is clusterAlias on the primary querying cluster of a CCS minimize_roundtrips=true query // the Map itself is immutable after construction - all Clusters will be accounted for at the start of the search @@ -503,6 +500,8 @@ public Clusters( assert remoteClusterIndices.size() > 0 : "At least one remote cluster must be passed into this Cluster constructor"; this.total = remoteClusterIndices.size() + (localIndices == null ? 0 : 1); assert total >= 1 : "No local indices or remote clusters passed in"; + this.successful = 0; // calculated from clusterInfo map for minimize_roundtrips + this.skipped = 0; // calculated from clusterInfo map for minimize_roundtrips this.ccsMinimizeRoundtrips = ccsMinimizeRoundtrips; Map> m = new HashMap<>(); if (localIndices != null) { @@ -517,11 +516,6 @@ public Clusters( m.put(clusterAlias, new AtomicReference<>(c)); } this.clusterInfo = Collections.unmodifiableMap(m); - this.successful = determineCountFromClusterInfo(cluster -> cluster.getStatus() == Cluster.Status.SUCCESSFUL); - this.skipped = determineCountFromClusterInfo(cluster -> cluster.getStatus() == Cluster.Status.SKIPPED); - this.running = determineCountFromClusterInfo(cluster -> cluster.getStatus() == Cluster.Status.RUNNING); - this.partial = determineCountFromClusterInfo(cluster -> cluster.getStatus() == Cluster.Status.PARTIAL); - this.failed = determineCountFromClusterInfo(cluster -> cluster.getStatus() == Cluster.Status.FAILED); } /** @@ -539,36 +533,39 @@ public Clusters(int total, int successful, int skipped) { this.total = total; this.successful = successful; this.skipped = skipped; - this.running = 0; - this.partial = 0; - this.failed = 0; this.ccsMinimizeRoundtrips = false; this.clusterInfo = Collections.emptyMap(); // will never be used if created from this constructor } public Clusters(StreamInput in) throws IOException { this.total = in.readVInt(); - this.successful = in.readVInt(); - this.skipped = in.readVInt(); + int successfulTemp = in.readVInt(); + int skippedTemp = in.readVInt(); if (in.getTransportVersion().onOrAfter(TransportVersions.V_8_500_053)) { List clusterList = in.readCollectionAsList(Cluster::new); if (clusterList.isEmpty()) { this.clusterInfo = Collections.emptyMap(); + this.successful = successfulTemp; + this.skipped = skippedTemp; } else { Map> m = new HashMap<>(); clusterList.forEach(c -> m.put(c.getClusterAlias(), new AtomicReference<>(c))); this.clusterInfo = Collections.unmodifiableMap(m); + this.successful = getClusterStateCount(Cluster.Status.SUCCESSFUL); + this.skipped = getClusterStateCount(Cluster.Status.SKIPPED); } } else { + this.successful = successfulTemp; + this.skipped = skippedTemp; this.clusterInfo = Collections.emptyMap(); } - this.running = determineCountFromClusterInfo(cluster -> cluster.getStatus() == Cluster.Status.RUNNING); - this.partial = determineCountFromClusterInfo(cluster -> cluster.getStatus() == Cluster.Status.PARTIAL); - this.failed = determineCountFromClusterInfo(cluster -> cluster.getStatus() == Cluster.Status.FAILED); + int running = getClusterStateCount(Cluster.Status.RUNNING); + int partial = getClusterStateCount(Cluster.Status.PARTIAL); + int failed = getClusterStateCount(Cluster.Status.FAILED); this.ccsMinimizeRoundtrips = false; assert total >= 0 : "total is negative: " + total; - assert total >= successful + skipped + running + partial + failed - : "successful + skipped + running + partial + failed is larger than total. total: " + assert total == successful + skipped + running + partial + failed + : "successful + skipped + running + partial + failed is not equal to total. total: " + total + " successful: " + successful @@ -586,11 +583,8 @@ private Clusters(Map> clusterInfoMap) { assert clusterInfoMap.size() > 0 : "this constructor should not be called with an empty Cluster info map"; this.total = clusterInfoMap.size(); this.clusterInfo = clusterInfoMap; - this.successful = 0; // calculated from clusterInfo map for minimize_roundtrips - this.skipped = 0; // calculated from clusterInfo map for minimize_roundtrips - this.running = 0; // calculated from clusterInfo map for minimize_roundtrips - this.partial = 0; // calculated from clusterInfo map for minimize_roundtrips - this.failed = 0; // calculated from clusterInfo map for minimize_roundtrips + this.successful = getClusterStateCount(Cluster.Status.SUCCESSFUL); + this.skipped = getClusterStateCount(Cluster.Status.SKIPPED); // should only be called if "details" section of fromXContent is present (for ccsMinimizeRoundtrips) this.ccsMinimizeRoundtrips = true; } @@ -705,11 +699,9 @@ public int getTotal() { public int getClusterStateCount(Cluster.Status status) { if (clusterInfo.isEmpty()) { return switch (status) { - case RUNNING -> running; case SUCCESSFUL -> successful; - case PARTIAL -> partial; case SKIPPED -> skipped; - case FAILED -> failed; + default -> 0; }; } else { return determineCountFromClusterInfo(cluster -> cluster.getStatus() == status); @@ -752,16 +744,23 @@ public boolean equals(Object o) { } Clusters clusters = (Clusters) o; return total == clusters.total - && successful == clusters.successful - && skipped == clusters.skipped - && running == clusters.running - && partial == clusters.partial - && failed == clusters.failed; + && getClusterStateCount(Cluster.Status.SUCCESSFUL) == clusters.getClusterStateCount(Cluster.Status.SUCCESSFUL) + && getClusterStateCount(Cluster.Status.SKIPPED) == clusters.getClusterStateCount(Cluster.Status.SKIPPED) + && getClusterStateCount(Cluster.Status.RUNNING) == clusters.getClusterStateCount(Cluster.Status.RUNNING) + && getClusterStateCount(Cluster.Status.PARTIAL) == clusters.getClusterStateCount(Cluster.Status.PARTIAL) + && getClusterStateCount(Cluster.Status.FAILED) == clusters.getClusterStateCount(Cluster.Status.FAILED); } @Override public int hashCode() { - return Objects.hash(total, successful, skipped, running, partial, failed); + return Objects.hash( + total, + getClusterStateCount(Cluster.Status.SUCCESSFUL), + getClusterStateCount(Cluster.Status.SKIPPED), + getClusterStateCount(Cluster.Status.RUNNING), + getClusterStateCount(Cluster.Status.PARTIAL), + getClusterStateCount(Cluster.Status.FAILED) + ); } @Override diff --git a/server/src/test/java/org/elasticsearch/action/search/SearchResponseTests.java b/server/src/test/java/org/elasticsearch/action/search/SearchResponseTests.java index 9acb63db2cc90..befea803e6fa0 100644 --- a/server/src/test/java/org/elasticsearch/action/search/SearchResponseTests.java +++ b/server/src/test/java/org/elasticsearch/action/search/SearchResponseTests.java @@ -590,7 +590,6 @@ public void testToXContent() throws IOException { } } - @AwaitsFix(bugUrl = "https://github.com/elastic/elasticsearch/issues/100005") public void testSerialization() throws IOException { SearchResponse searchResponse = createTestItem(false); SearchResponse deserialized = copyWriteable( From 9d01def3dce9349d73f19f23f5b1ad4c41cf1646 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Istv=C3=A1n=20Zolt=C3=A1n=20Szab=C3=B3?= Date: Fri, 29 Sep 2023 09:24:36 +0200 Subject: [PATCH 28/32] [DOCS] Changes semantic search tutorials to use ELSER v2 and sparse_vector field type (#100021) * [DOCS] Changes semantic search tutorials to use ELSER v2 and sparse_vector field type. * [DOCS] More edits. --- .../semantic-search-elser.asciidoc | 35 ++++++++++--------- .../semantic-search/field-mappings.asciidoc | 10 +++--- .../generate-embeddings.asciidoc | 2 +- .../semantic-search/hybrid-search.asciidoc | 2 +- .../semantic-search/search.asciidoc | 4 +-- 5 files changed, 27 insertions(+), 26 deletions(-) diff --git a/docs/reference/search/search-your-data/semantic-search-elser.asciidoc b/docs/reference/search/search-your-data/semantic-search-elser.asciidoc index 0f07f1f4128fe..082bb2ae2e020 100644 --- a/docs/reference/search/search-your-data/semantic-search-elser.asciidoc +++ b/docs/reference/search/search-your-data/semantic-search-elser.asciidoc @@ -14,7 +14,7 @@ The instructions in this tutorial shows you how to use ELSER to perform semantic search on your data. NOTE: Only the first 512 extracted tokens per field are considered during -semantic search with ELSER v1. Refer to +semantic search with ELSER. Refer to {ml-docs}/ml-nlp-limitations.html#ml-nlp-elser-v1-limit-512[this page] for more information. @@ -44,15 +44,16 @@ you must provide suitably sized nodes yourself. First, the mapping of the destination index - the index that contains the tokens that the model created based on your text - must be created. The destination -index must have a field with the <> field type -to index the ELSER output. +index must have a field with the +<> field type to index the +ELSER output. -NOTE: ELSER output must be ingested into a field with the `rank_features` field -type. Otherwise, {es} interprets the token-weight pairs as a massive amount of -fields in a document. If you get an error similar to this +NOTE: ELSER output must be ingested into a field with the `sparse_vector` or +`rank_features` field type. Otherwise, {es} interprets the token-weight pairs as +a massive amount of fields in a document. If you get an error similar to this `"Limit of total fields [1000] has been exceeded while adding new fields"` then the ELSER output field is not mapped properly and it has a field type different -than `rank_features`. +than `sparse_vector` or `rank_features`. [source,console] ---- @@ -61,7 +62,7 @@ PUT my-index "mappings": { "properties": { "ml.tokens": { <1> - "type": "rank_features" <2> + "type": "sparse_vector" <2> }, "text": { <3> "type": "text" <4> @@ -72,7 +73,7 @@ PUT my-index ---- // TEST[skip:TBD] <1> The name of the field to contain the generated tokens. -<2> The field to contain the tokens is a `rank_features` field. +<2> The field to contain the tokens is a `sparse_vector` field. <3> The name of the field from which to create the sparse vector representation. In this example, the name of the field is `text`. <4> The field type which is text in this example. @@ -90,12 +91,12 @@ that is being ingested in the pipeline. [source,console] ---- -PUT _ingest/pipeline/elser-v1-test +PUT _ingest/pipeline/elser-v2-test { "processors": [ { "inference": { - "model_id": ".elser_model_1", + "model_id": ".elser_model_2", "target_field": "ml", "field_map": { <1> "text": "text_field" @@ -155,7 +156,7 @@ POST _reindex?wait_for_completion=false }, "dest": { "index": "my-index", - "pipeline": "elser-v1-test" + "pipeline": "elser-v2-test" } } ---- @@ -192,7 +193,7 @@ GET my-index/_search "query":{ "text_expansion":{ "ml.tokens":{ - "model_id":".elser_model_1", + "model_id":".elser_model_2", "model_text":"How to avoid muscle soreness after running?" } } @@ -236,7 +237,7 @@ weights. "exercises":0.36694175, (...) }, - "model_id":".elser_model_1" + "model_id":".elser_model_2" } } }, @@ -276,7 +277,7 @@ GET my-index/_search "text_expansion": { "ml.tokens": { "model_text": "How to avoid muscle soreness after running?", - "model_id": ".elser_model_1", + "model_id": ".elser_model_2", "boost": 1 <2> } } @@ -342,7 +343,7 @@ PUT my-index }, "properties": { "ml.tokens": { - "type": "rank_features" + "type": "sparse_vector" }, "text": { "type": "text" @@ -359,7 +360,7 @@ PUT my-index ==== Further reading * {ml-docs}/ml-nlp-elser.html[How to download and deploy ELSER] -* {ml-docs}/ml-nlp-limitations.html#ml-nlp-elser-v1-limit-512[ELSER v1 limitation] +* {ml-docs}/ml-nlp-limitations.html#ml-nlp-elser-v1-limit-512[ELSER limitation] * https://www.elastic.co/blog/may-2023-launch-information-retrieval-elasticsearch-ai-model[Improving information retrieval in the Elastic Stack: Introducing Elastic Learned Sparse Encoder, our new retrieval model] [discrete] diff --git a/docs/reference/tab-widgets/semantic-search/field-mappings.asciidoc b/docs/reference/tab-widgets/semantic-search/field-mappings.asciidoc index 228b7a9202341..0228078e8ce39 100644 --- a/docs/reference/tab-widgets/semantic-search/field-mappings.asciidoc +++ b/docs/reference/tab-widgets/semantic-search/field-mappings.asciidoc @@ -1,15 +1,15 @@ // tag::elser[] ELSER produces token-weight pairs as output from the input text and the query. -The {es} <> field type can store these +The {es} <> field type can store these token-weight pairs as numeric feature vectors. The index must have a field with -the `rank_features` field type to index the tokens that ELSER generates. +the `sparse_vector` field type to index the tokens that ELSER generates. To create a mapping for your ELSER index, refer to the <> of the tutorial. The example shows how to create an index mapping for `my-index` that defines the `my_embeddings.tokens` field - which will contain the ELSER output - as a -`rank_features` field. +`sparse_vector` field. [source,console] ---- @@ -18,7 +18,7 @@ PUT my-index "mappings": { "properties": { "my_embeddings.tokens": { <1> - "type": "rank_features" <2> + "type": "sparse_vector" <2> }, "my_text_field": { <3> "type": "text" <4> @@ -28,7 +28,7 @@ PUT my-index } ---- <1> The name of the field that will contain the tokens generated by ELSER. -<2> The field that contains the tokens must be a `rank_features` field. +<2> The field that contains the tokens must be a `sparse_vector` field. <3> The name of the field from which to create the sparse vector representation. In this example, the name of the field is `my_text_field`. <4> The field type is `text` in this example. diff --git a/docs/reference/tab-widgets/semantic-search/generate-embeddings.asciidoc b/docs/reference/tab-widgets/semantic-search/generate-embeddings.asciidoc index 0adfda5c2bff9..786f40fe141bd 100644 --- a/docs/reference/tab-widgets/semantic-search/generate-embeddings.asciidoc +++ b/docs/reference/tab-widgets/semantic-search/generate-embeddings.asciidoc @@ -21,7 +21,7 @@ PUT _ingest/pipeline/my-text-embeddings-pipeline "processors": [ { "inference": { - "model_id": ".elser_model_1", + "model_id": ".elser_model_2", "target_field": "my_embeddings", "field_map": { <1> "my_text_field": "text_field" diff --git a/docs/reference/tab-widgets/semantic-search/hybrid-search.asciidoc b/docs/reference/tab-widgets/semantic-search/hybrid-search.asciidoc index 26fc25c2385c8..a99bdf3c8722b 100644 --- a/docs/reference/tab-widgets/semantic-search/hybrid-search.asciidoc +++ b/docs/reference/tab-widgets/semantic-search/hybrid-search.asciidoc @@ -22,7 +22,7 @@ GET my-index/_search "query": { "text_expansion": { "my_embeddings.tokens": { - "model_id": ".elser_model_1", + "model_id": ".elser_model_2", "model_text": "the query string" } } diff --git a/docs/reference/tab-widgets/semantic-search/search.asciidoc b/docs/reference/tab-widgets/semantic-search/search.asciidoc index 425b797789270..d1cd31fbe4309 100644 --- a/docs/reference/tab-widgets/semantic-search/search.asciidoc +++ b/docs/reference/tab-widgets/semantic-search/search.asciidoc @@ -12,7 +12,7 @@ GET my-index/_search "query":{ "text_expansion":{ "my_embeddings.tokens":{ <1> - "model_id":".elser_model_1", + "model_id":".elser_model_2", "model_text":"the query string" } } @@ -20,7 +20,7 @@ GET my-index/_search } ---- // TEST[skip:TBD] -<1> The field of type `rank_features`. +<1> The field of type `sparse_vector`. // end::elser[] From c7705aa32abc6b5e3ce34c4edde6fb8ee68101bf Mon Sep 17 00:00:00 2001 From: Kostas Krikellas <131142368+kkrik-es@users.noreply.github.com> Date: Fri, 29 Sep 2023 10:42:01 +0300 Subject: [PATCH 29/32] Improve time-series error and documentation (#100018) * Improve time-series error and documentation * spotless fix * Update docs/changelog/100018.yaml * Fix changelist * Change exception type --- docs/changelog/100018.yaml | 5 +++++ .../aggregations/bucket/time-series-aggregation.asciidoc | 4 ---- .../search/aggregations/AggregationExecutionContext.java | 6 ++++++ 3 files changed, 11 insertions(+), 4 deletions(-) create mode 100644 docs/changelog/100018.yaml diff --git a/docs/changelog/100018.yaml b/docs/changelog/100018.yaml new file mode 100644 index 0000000000000..b39089db568c0 --- /dev/null +++ b/docs/changelog/100018.yaml @@ -0,0 +1,5 @@ +pr: 100018 +summary: Improve time-series error and documentation +area: "TSDB" +type: enhancement +issues: [] diff --git a/docs/reference/aggregations/bucket/time-series-aggregation.asciidoc b/docs/reference/aggregations/bucket/time-series-aggregation.asciidoc index 54638083b1053..d93df55118a8b 100644 --- a/docs/reference/aggregations/bucket/time-series-aggregation.asciidoc +++ b/docs/reference/aggregations/bucket/time-series-aggregation.asciidoc @@ -67,8 +67,6 @@ PUT /my-time-series-index-0/_bulk -------------------------------------------------- // NOTCONSOLE -////////////////////////// - To perform a time series aggregation, specify "time_series" as the aggregation type. When the boolean "keyed" is true, each bucket is given a unique key. @@ -85,8 +83,6 @@ GET /_search -------------------------------------------------- // NOTCONSOLE -////////////////////////// - This will return all results in the time series, however a more typical query will use sub aggregations to reduce the date returned to something more relevant. diff --git a/server/src/main/java/org/elasticsearch/search/aggregations/AggregationExecutionContext.java b/server/src/main/java/org/elasticsearch/search/aggregations/AggregationExecutionContext.java index 88a78a512d1bd..273df99f6479c 100644 --- a/server/src/main/java/org/elasticsearch/search/aggregations/AggregationExecutionContext.java +++ b/server/src/main/java/org/elasticsearch/search/aggregations/AggregationExecutionContext.java @@ -53,6 +53,12 @@ public long getTimestamp() { } public int getTsidOrd() { + if (tsidOrdProvider == null) { + throw new IllegalArgumentException( + "Aggregation on a time-series field is misconfigured, likely due to lack of wrapping " + + "a metric aggregation within a `time-series` aggregation" + ); + } return tsidOrdProvider.getAsInt(); } } From b30813bd37417ea0e01581c1a61b1574200caa2c Mon Sep 17 00:00:00 2001 From: Luigi Dell'Aquila Date: Fri, 29 Sep 2023 09:54:38 +0200 Subject: [PATCH 30/32] ESQL: add driverContext to conversion function evaluators and use blockFactory to instantiate blocks (#100016) Let convert functions (eg. `to_string()`, `to_int()`, `to_ip()` and so on) use DriverContext.blockFactory() to generate new blocks, so that they are properly attached to the circuit breaker. --- .../gen/ConvertEvaluatorImplementer.java | 68 ++++++------------- .../convert/ToBooleanFromDoubleEvaluator.java | 32 ++++----- .../convert/ToBooleanFromIntEvaluator.java | 32 ++++----- .../convert/ToBooleanFromLongEvaluator.java | 32 ++++----- .../convert/ToBooleanFromStringEvaluator.java | 32 ++++----- .../ToBooleanFromUnsignedLongEvaluator.java | 32 ++++----- .../ToDatetimeFromStringEvaluator.java | 32 ++++----- .../scalar/convert/ToDegreesEvaluator.java | 32 ++++----- .../convert/ToDoubleFromBooleanEvaluator.java | 32 ++++----- .../convert/ToDoubleFromIntEvaluator.java | 32 ++++----- .../convert/ToDoubleFromLongEvaluator.java | 32 ++++----- .../convert/ToDoubleFromStringEvaluator.java | 32 ++++----- .../ToDoubleFromUnsignedLongEvaluator.java | 32 ++++----- .../convert/ToIPFromStringEvaluator.java | 35 ++++------ .../ToIntegerFromBooleanEvaluator.java | 32 ++++----- .../convert/ToIntegerFromDoubleEvaluator.java | 32 ++++----- .../convert/ToIntegerFromLongEvaluator.java | 32 ++++----- .../convert/ToIntegerFromStringEvaluator.java | 32 ++++----- .../ToIntegerFromUnsignedLongEvaluator.java | 32 ++++----- .../convert/ToLongFromBooleanEvaluator.java | 32 ++++----- .../convert/ToLongFromDoubleEvaluator.java | 32 ++++----- .../convert/ToLongFromIntEvaluator.java | 32 ++++----- .../convert/ToLongFromStringEvaluator.java | 32 ++++----- .../ToLongFromUnsignedLongEvaluator.java | 32 ++++----- .../scalar/convert/ToRadiansEvaluator.java | 32 ++++----- .../convert/ToStringFromBooleanEvaluator.java | 35 ++++------ .../ToStringFromDatetimeEvaluator.java | 35 ++++------ .../convert/ToStringFromDoubleEvaluator.java | 35 ++++------ .../convert/ToStringFromIPEvaluator.java | 35 ++++------ .../convert/ToStringFromIntEvaluator.java | 35 ++++------ .../convert/ToStringFromLongEvaluator.java | 35 ++++------ .../ToStringFromUnsignedLongEvaluator.java | 35 ++++------ .../convert/ToStringFromVersionEvaluator.java | 35 ++++------ .../ToUnsignedLongFromBooleanEvaluator.java | 32 ++++----- .../ToUnsignedLongFromDoubleEvaluator.java | 32 ++++----- .../ToUnsignedLongFromIntEvaluator.java | 32 ++++----- .../ToUnsignedLongFromLongEvaluator.java | 32 ++++----- .../ToUnsignedLongFromStringEvaluator.java | 32 ++++----- .../convert/ToVersionFromStringEvaluator.java | 35 ++++------ .../convert/AbstractConvertFunction.java | 7 +- .../function/scalar/convert/ToBoolean.java | 14 ++-- .../function/scalar/convert/ToDatetime.java | 16 +++-- .../function/scalar/convert/ToDegrees.java | 30 ++++++-- .../function/scalar/convert/ToDouble.java | 14 ++-- .../function/scalar/convert/ToIP.java | 17 +++-- .../function/scalar/convert/ToInteger.java | 14 ++-- .../function/scalar/convert/ToLong.java | 16 +++-- .../function/scalar/convert/ToRadians.java | 30 ++++++-- .../function/scalar/convert/ToString.java | 16 +++-- .../scalar/convert/ToUnsignedLong.java | 14 ++-- .../function/scalar/convert/ToVersion.java | 14 ++-- 51 files changed, 654 insertions(+), 862 deletions(-) diff --git a/x-pack/plugin/esql/compute/gen/src/main/java/org/elasticsearch/compute/gen/ConvertEvaluatorImplementer.java b/x-pack/plugin/esql/compute/gen/src/main/java/org/elasticsearch/compute/gen/ConvertEvaluatorImplementer.java index af42d94c236f2..d5e38127cdec7 100644 --- a/x-pack/plugin/esql/compute/gen/src/main/java/org/elasticsearch/compute/gen/ConvertEvaluatorImplementer.java +++ b/x-pack/plugin/esql/compute/gen/src/main/java/org/elasticsearch/compute/gen/ConvertEvaluatorImplementer.java @@ -13,8 +13,6 @@ import com.squareup.javapoet.TypeName; import com.squareup.javapoet.TypeSpec; -import java.util.BitSet; - import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; import javax.lang.model.element.TypeElement; @@ -23,18 +21,13 @@ import static org.elasticsearch.compute.gen.Methods.appendMethod; import static org.elasticsearch.compute.gen.Methods.getMethod; import static org.elasticsearch.compute.gen.Types.ABSTRACT_CONVERT_FUNCTION_EVALUATOR; -import static org.elasticsearch.compute.gen.Types.BIG_ARRAYS; import static org.elasticsearch.compute.gen.Types.BLOCK; import static org.elasticsearch.compute.gen.Types.BYTES_REF; -import static org.elasticsearch.compute.gen.Types.BYTES_REF_ARRAY; -import static org.elasticsearch.compute.gen.Types.BYTES_REF_BLOCK; +import static org.elasticsearch.compute.gen.Types.DRIVER_CONTEXT; import static org.elasticsearch.compute.gen.Types.EXPRESSION_EVALUATOR; import static org.elasticsearch.compute.gen.Types.SOURCE; import static org.elasticsearch.compute.gen.Types.VECTOR; -import static org.elasticsearch.compute.gen.Types.arrayBlockType; -import static org.elasticsearch.compute.gen.Types.arrayVectorType; import static org.elasticsearch.compute.gen.Types.blockType; -import static org.elasticsearch.compute.gen.Types.constantVectorType; import static org.elasticsearch.compute.gen.Types.vectorType; public class ConvertEvaluatorImplementer { @@ -79,6 +72,8 @@ private TypeSpec type() { builder.addModifiers(Modifier.PUBLIC, Modifier.FINAL); builder.superclass(ABSTRACT_CONVERT_FUNCTION_EVALUATOR); + builder.addField(DRIVER_CONTEXT, "driverContext", Modifier.PRIVATE, Modifier.FINAL); + builder.addMethod(ctor()); builder.addMethod(name()); builder.addMethod(evalVector()); @@ -92,7 +87,9 @@ private MethodSpec ctor() { MethodSpec.Builder builder = MethodSpec.constructorBuilder().addModifiers(Modifier.PUBLIC); builder.addParameter(EXPRESSION_EVALUATOR, "field"); builder.addParameter(SOURCE, "source"); + builder.addParameter(DRIVER_CONTEXT, "driverContext"); builder.addStatement("super($N, $N)", "field", "source"); + builder.addStatement("this.driverContext = driverContext"); return builder.build(); } @@ -121,9 +118,9 @@ private MethodSpec evalVector() { { builder.beginControlFlow("try"); { - var constVectType = constantVectorType(resultType); + var constVectType = blockType(resultType); builder.addStatement( - "return new $T($N, positionCount).asBlock()", + "return driverContext.blockFactory().newConstant$TWith($N, positionCount)", constVectType, evalValueCall("vector", "0", scratchPadName) ); @@ -131,59 +128,34 @@ private MethodSpec evalVector() { builder.nextControlFlow("catch (Exception e)"); { builder.addStatement("registerException(e)"); - builder.addStatement("return Block.constantNullBlock(positionCount)"); + builder.addStatement("return Block.constantNullBlock(positionCount, driverContext.blockFactory())"); } builder.endControlFlow(); } builder.endControlFlow(); - builder.addStatement("$T nullsMask = null", BitSet.class); - if (resultType.equals(BYTES_REF)) { - builder.addStatement( - "$T values = new $T(positionCount, $T.NON_RECYCLING_INSTANCE)", // TODO: see note in MvEvaluatorImplementer - BYTES_REF_ARRAY, - BYTES_REF_ARRAY, - BIG_ARRAYS - ); - } else { - builder.addStatement("$T[] values = new $T[positionCount]", resultType, resultType); - } + ClassName returnBlockType = blockType(resultType); + builder.addStatement( + "$T.Builder builder = $T.newBlockBuilder(positionCount, driverContext.blockFactory())", + returnBlockType, + returnBlockType + ); builder.beginControlFlow("for (int p = 0; p < positionCount; p++)"); { builder.beginControlFlow("try"); { - if (resultType.equals(BYTES_REF)) { - builder.addStatement("values.append($N)", evalValueCall("vector", "p", scratchPadName)); - } else { - builder.addStatement("values[p] = $N", evalValueCall("vector", "p", scratchPadName)); - } + builder.addStatement("builder.$L($N)", appendMethod(resultType), evalValueCall("vector", "p", scratchPadName)); } builder.nextControlFlow("catch (Exception e)"); { builder.addStatement("registerException(e)"); - builder.beginControlFlow("if (nullsMask == null)"); - { - builder.addStatement("nullsMask = new BitSet(positionCount)"); - } - builder.endControlFlow(); - builder.addStatement("nullsMask.set(p)"); - if (resultType.equals(BYTES_REF)) { - builder.addStatement("values.append($T.NULL_VALUE)", BYTES_REF_BLOCK); - } + builder.addStatement("builder.appendNull()"); } builder.endControlFlow(); } builder.endControlFlow(); - builder.addStatement( - """ - return nullsMask == null - ? new $T(values, positionCount).asBlock() - // UNORDERED, since whatever ordering there is, it isn't necessarily preserved - : new $T(values, positionCount, null, nullsMask, Block.MvOrdering.UNORDERED)""", - arrayVectorType(resultType), - arrayBlockType(resultType) - ); + builder.addStatement("return builder.build()"); return builder.build(); } @@ -196,7 +168,11 @@ private MethodSpec evalBlock() { builder.addStatement("$T block = ($T) b", blockType, blockType); builder.addStatement("int positionCount = block.getPositionCount()"); TypeName resultBlockType = blockType(resultType); - builder.addStatement("$T.Builder builder = $T.newBlockBuilder(positionCount)", resultBlockType, resultBlockType); + builder.addStatement( + "$T.Builder builder = $T.newBlockBuilder(positionCount, driverContext.blockFactory())", + resultBlockType, + resultBlockType + ); String scratchPadName = null; if (argumentType.equals(BYTES_REF)) { scratchPadName = "scratchPad"; diff --git a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToBooleanFromDoubleEvaluator.java b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToBooleanFromDoubleEvaluator.java index dba115f4f7c29..0b8b444a2b6a3 100644 --- a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToBooleanFromDoubleEvaluator.java +++ b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToBooleanFromDoubleEvaluator.java @@ -6,15 +6,12 @@ import java.lang.Override; import java.lang.String; -import java.util.BitSet; import org.elasticsearch.compute.data.Block; -import org.elasticsearch.compute.data.BooleanArrayBlock; -import org.elasticsearch.compute.data.BooleanArrayVector; import org.elasticsearch.compute.data.BooleanBlock; -import org.elasticsearch.compute.data.ConstantBooleanVector; import org.elasticsearch.compute.data.DoubleBlock; import org.elasticsearch.compute.data.DoubleVector; import org.elasticsearch.compute.data.Vector; +import org.elasticsearch.compute.operator.DriverContext; import org.elasticsearch.compute.operator.EvalOperator; import org.elasticsearch.xpack.ql.tree.Source; @@ -23,8 +20,12 @@ * This class is generated. Do not edit it. */ public final class ToBooleanFromDoubleEvaluator extends AbstractConvertFunction.AbstractEvaluator { - public ToBooleanFromDoubleEvaluator(EvalOperator.ExpressionEvaluator field, Source source) { + private final DriverContext driverContext; + + public ToBooleanFromDoubleEvaluator(EvalOperator.ExpressionEvaluator field, Source source, + DriverContext driverContext) { super(field, source); + this.driverContext = driverContext; } @Override @@ -38,29 +39,22 @@ public Block evalVector(Vector v) { int positionCount = v.getPositionCount(); if (vector.isConstant()) { try { - return new ConstantBooleanVector(evalValue(vector, 0), positionCount).asBlock(); + return driverContext.blockFactory().newConstantBooleanBlockWith(evalValue(vector, 0), positionCount); } catch (Exception e) { registerException(e); - return Block.constantNullBlock(positionCount); + return Block.constantNullBlock(positionCount, driverContext.blockFactory()); } } - BitSet nullsMask = null; - boolean[] values = new boolean[positionCount]; + BooleanBlock.Builder builder = BooleanBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); for (int p = 0; p < positionCount; p++) { try { - values[p] = evalValue(vector, p); + builder.appendBoolean(evalValue(vector, p)); } catch (Exception e) { registerException(e); - if (nullsMask == null) { - nullsMask = new BitSet(positionCount); - } - nullsMask.set(p); + builder.appendNull(); } } - return nullsMask == null - ? new BooleanArrayVector(values, positionCount).asBlock() - // UNORDERED, since whatever ordering there is, it isn't necessarily preserved - : new BooleanArrayBlock(values, positionCount, null, nullsMask, Block.MvOrdering.UNORDERED); + return builder.build(); } private static boolean evalValue(DoubleVector container, int index) { @@ -72,7 +66,7 @@ private static boolean evalValue(DoubleVector container, int index) { public Block evalBlock(Block b) { DoubleBlock block = (DoubleBlock) b; int positionCount = block.getPositionCount(); - BooleanBlock.Builder builder = BooleanBlock.newBlockBuilder(positionCount); + BooleanBlock.Builder builder = BooleanBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); for (int p = 0; p < positionCount; p++) { int valueCount = block.getValueCount(p); int start = block.getFirstValueIndex(p); diff --git a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToBooleanFromIntEvaluator.java b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToBooleanFromIntEvaluator.java index d20179fd7baed..1295956645a6f 100644 --- a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToBooleanFromIntEvaluator.java +++ b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToBooleanFromIntEvaluator.java @@ -6,15 +6,12 @@ import java.lang.Override; import java.lang.String; -import java.util.BitSet; import org.elasticsearch.compute.data.Block; -import org.elasticsearch.compute.data.BooleanArrayBlock; -import org.elasticsearch.compute.data.BooleanArrayVector; import org.elasticsearch.compute.data.BooleanBlock; -import org.elasticsearch.compute.data.ConstantBooleanVector; import org.elasticsearch.compute.data.IntBlock; import org.elasticsearch.compute.data.IntVector; import org.elasticsearch.compute.data.Vector; +import org.elasticsearch.compute.operator.DriverContext; import org.elasticsearch.compute.operator.EvalOperator; import org.elasticsearch.xpack.ql.tree.Source; @@ -23,8 +20,12 @@ * This class is generated. Do not edit it. */ public final class ToBooleanFromIntEvaluator extends AbstractConvertFunction.AbstractEvaluator { - public ToBooleanFromIntEvaluator(EvalOperator.ExpressionEvaluator field, Source source) { + private final DriverContext driverContext; + + public ToBooleanFromIntEvaluator(EvalOperator.ExpressionEvaluator field, Source source, + DriverContext driverContext) { super(field, source); + this.driverContext = driverContext; } @Override @@ -38,29 +39,22 @@ public Block evalVector(Vector v) { int positionCount = v.getPositionCount(); if (vector.isConstant()) { try { - return new ConstantBooleanVector(evalValue(vector, 0), positionCount).asBlock(); + return driverContext.blockFactory().newConstantBooleanBlockWith(evalValue(vector, 0), positionCount); } catch (Exception e) { registerException(e); - return Block.constantNullBlock(positionCount); + return Block.constantNullBlock(positionCount, driverContext.blockFactory()); } } - BitSet nullsMask = null; - boolean[] values = new boolean[positionCount]; + BooleanBlock.Builder builder = BooleanBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); for (int p = 0; p < positionCount; p++) { try { - values[p] = evalValue(vector, p); + builder.appendBoolean(evalValue(vector, p)); } catch (Exception e) { registerException(e); - if (nullsMask == null) { - nullsMask = new BitSet(positionCount); - } - nullsMask.set(p); + builder.appendNull(); } } - return nullsMask == null - ? new BooleanArrayVector(values, positionCount).asBlock() - // UNORDERED, since whatever ordering there is, it isn't necessarily preserved - : new BooleanArrayBlock(values, positionCount, null, nullsMask, Block.MvOrdering.UNORDERED); + return builder.build(); } private static boolean evalValue(IntVector container, int index) { @@ -72,7 +66,7 @@ private static boolean evalValue(IntVector container, int index) { public Block evalBlock(Block b) { IntBlock block = (IntBlock) b; int positionCount = block.getPositionCount(); - BooleanBlock.Builder builder = BooleanBlock.newBlockBuilder(positionCount); + BooleanBlock.Builder builder = BooleanBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); for (int p = 0; p < positionCount; p++) { int valueCount = block.getValueCount(p); int start = block.getFirstValueIndex(p); diff --git a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToBooleanFromLongEvaluator.java b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToBooleanFromLongEvaluator.java index 7ab2d656a59cb..be01f122f9a8f 100644 --- a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToBooleanFromLongEvaluator.java +++ b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToBooleanFromLongEvaluator.java @@ -6,15 +6,12 @@ import java.lang.Override; import java.lang.String; -import java.util.BitSet; import org.elasticsearch.compute.data.Block; -import org.elasticsearch.compute.data.BooleanArrayBlock; -import org.elasticsearch.compute.data.BooleanArrayVector; import org.elasticsearch.compute.data.BooleanBlock; -import org.elasticsearch.compute.data.ConstantBooleanVector; import org.elasticsearch.compute.data.LongBlock; import org.elasticsearch.compute.data.LongVector; import org.elasticsearch.compute.data.Vector; +import org.elasticsearch.compute.operator.DriverContext; import org.elasticsearch.compute.operator.EvalOperator; import org.elasticsearch.xpack.ql.tree.Source; @@ -23,8 +20,12 @@ * This class is generated. Do not edit it. */ public final class ToBooleanFromLongEvaluator extends AbstractConvertFunction.AbstractEvaluator { - public ToBooleanFromLongEvaluator(EvalOperator.ExpressionEvaluator field, Source source) { + private final DriverContext driverContext; + + public ToBooleanFromLongEvaluator(EvalOperator.ExpressionEvaluator field, Source source, + DriverContext driverContext) { super(field, source); + this.driverContext = driverContext; } @Override @@ -38,29 +39,22 @@ public Block evalVector(Vector v) { int positionCount = v.getPositionCount(); if (vector.isConstant()) { try { - return new ConstantBooleanVector(evalValue(vector, 0), positionCount).asBlock(); + return driverContext.blockFactory().newConstantBooleanBlockWith(evalValue(vector, 0), positionCount); } catch (Exception e) { registerException(e); - return Block.constantNullBlock(positionCount); + return Block.constantNullBlock(positionCount, driverContext.blockFactory()); } } - BitSet nullsMask = null; - boolean[] values = new boolean[positionCount]; + BooleanBlock.Builder builder = BooleanBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); for (int p = 0; p < positionCount; p++) { try { - values[p] = evalValue(vector, p); + builder.appendBoolean(evalValue(vector, p)); } catch (Exception e) { registerException(e); - if (nullsMask == null) { - nullsMask = new BitSet(positionCount); - } - nullsMask.set(p); + builder.appendNull(); } } - return nullsMask == null - ? new BooleanArrayVector(values, positionCount).asBlock() - // UNORDERED, since whatever ordering there is, it isn't necessarily preserved - : new BooleanArrayBlock(values, positionCount, null, nullsMask, Block.MvOrdering.UNORDERED); + return builder.build(); } private static boolean evalValue(LongVector container, int index) { @@ -72,7 +66,7 @@ private static boolean evalValue(LongVector container, int index) { public Block evalBlock(Block b) { LongBlock block = (LongBlock) b; int positionCount = block.getPositionCount(); - BooleanBlock.Builder builder = BooleanBlock.newBlockBuilder(positionCount); + BooleanBlock.Builder builder = BooleanBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); for (int p = 0; p < positionCount; p++) { int valueCount = block.getValueCount(p); int start = block.getFirstValueIndex(p); diff --git a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToBooleanFromStringEvaluator.java b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToBooleanFromStringEvaluator.java index d70d0365aaf4d..7b83995bf0933 100644 --- a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToBooleanFromStringEvaluator.java +++ b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToBooleanFromStringEvaluator.java @@ -6,16 +6,13 @@ import java.lang.Override; import java.lang.String; -import java.util.BitSet; import org.apache.lucene.util.BytesRef; import org.elasticsearch.compute.data.Block; -import org.elasticsearch.compute.data.BooleanArrayBlock; -import org.elasticsearch.compute.data.BooleanArrayVector; import org.elasticsearch.compute.data.BooleanBlock; import org.elasticsearch.compute.data.BytesRefBlock; import org.elasticsearch.compute.data.BytesRefVector; -import org.elasticsearch.compute.data.ConstantBooleanVector; import org.elasticsearch.compute.data.Vector; +import org.elasticsearch.compute.operator.DriverContext; import org.elasticsearch.compute.operator.EvalOperator; import org.elasticsearch.xpack.ql.tree.Source; @@ -24,8 +21,12 @@ * This class is generated. Do not edit it. */ public final class ToBooleanFromStringEvaluator extends AbstractConvertFunction.AbstractEvaluator { - public ToBooleanFromStringEvaluator(EvalOperator.ExpressionEvaluator field, Source source) { + private final DriverContext driverContext; + + public ToBooleanFromStringEvaluator(EvalOperator.ExpressionEvaluator field, Source source, + DriverContext driverContext) { super(field, source); + this.driverContext = driverContext; } @Override @@ -40,29 +41,22 @@ public Block evalVector(Vector v) { BytesRef scratchPad = new BytesRef(); if (vector.isConstant()) { try { - return new ConstantBooleanVector(evalValue(vector, 0, scratchPad), positionCount).asBlock(); + return driverContext.blockFactory().newConstantBooleanBlockWith(evalValue(vector, 0, scratchPad), positionCount); } catch (Exception e) { registerException(e); - return Block.constantNullBlock(positionCount); + return Block.constantNullBlock(positionCount, driverContext.blockFactory()); } } - BitSet nullsMask = null; - boolean[] values = new boolean[positionCount]; + BooleanBlock.Builder builder = BooleanBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); for (int p = 0; p < positionCount; p++) { try { - values[p] = evalValue(vector, p, scratchPad); + builder.appendBoolean(evalValue(vector, p, scratchPad)); } catch (Exception e) { registerException(e); - if (nullsMask == null) { - nullsMask = new BitSet(positionCount); - } - nullsMask.set(p); + builder.appendNull(); } } - return nullsMask == null - ? new BooleanArrayVector(values, positionCount).asBlock() - // UNORDERED, since whatever ordering there is, it isn't necessarily preserved - : new BooleanArrayBlock(values, positionCount, null, nullsMask, Block.MvOrdering.UNORDERED); + return builder.build(); } private static boolean evalValue(BytesRefVector container, int index, BytesRef scratchPad) { @@ -74,7 +68,7 @@ private static boolean evalValue(BytesRefVector container, int index, BytesRef s public Block evalBlock(Block b) { BytesRefBlock block = (BytesRefBlock) b; int positionCount = block.getPositionCount(); - BooleanBlock.Builder builder = BooleanBlock.newBlockBuilder(positionCount); + BooleanBlock.Builder builder = BooleanBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); BytesRef scratchPad = new BytesRef(); for (int p = 0; p < positionCount; p++) { int valueCount = block.getValueCount(p); diff --git a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToBooleanFromUnsignedLongEvaluator.java b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToBooleanFromUnsignedLongEvaluator.java index d2cf4b41770ce..4a8aebe9cd8ab 100644 --- a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToBooleanFromUnsignedLongEvaluator.java +++ b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToBooleanFromUnsignedLongEvaluator.java @@ -6,15 +6,12 @@ import java.lang.Override; import java.lang.String; -import java.util.BitSet; import org.elasticsearch.compute.data.Block; -import org.elasticsearch.compute.data.BooleanArrayBlock; -import org.elasticsearch.compute.data.BooleanArrayVector; import org.elasticsearch.compute.data.BooleanBlock; -import org.elasticsearch.compute.data.ConstantBooleanVector; import org.elasticsearch.compute.data.LongBlock; import org.elasticsearch.compute.data.LongVector; import org.elasticsearch.compute.data.Vector; +import org.elasticsearch.compute.operator.DriverContext; import org.elasticsearch.compute.operator.EvalOperator; import org.elasticsearch.xpack.ql.tree.Source; @@ -23,8 +20,12 @@ * This class is generated. Do not edit it. */ public final class ToBooleanFromUnsignedLongEvaluator extends AbstractConvertFunction.AbstractEvaluator { - public ToBooleanFromUnsignedLongEvaluator(EvalOperator.ExpressionEvaluator field, Source source) { + private final DriverContext driverContext; + + public ToBooleanFromUnsignedLongEvaluator(EvalOperator.ExpressionEvaluator field, Source source, + DriverContext driverContext) { super(field, source); + this.driverContext = driverContext; } @Override @@ -38,29 +39,22 @@ public Block evalVector(Vector v) { int positionCount = v.getPositionCount(); if (vector.isConstant()) { try { - return new ConstantBooleanVector(evalValue(vector, 0), positionCount).asBlock(); + return driverContext.blockFactory().newConstantBooleanBlockWith(evalValue(vector, 0), positionCount); } catch (Exception e) { registerException(e); - return Block.constantNullBlock(positionCount); + return Block.constantNullBlock(positionCount, driverContext.blockFactory()); } } - BitSet nullsMask = null; - boolean[] values = new boolean[positionCount]; + BooleanBlock.Builder builder = BooleanBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); for (int p = 0; p < positionCount; p++) { try { - values[p] = evalValue(vector, p); + builder.appendBoolean(evalValue(vector, p)); } catch (Exception e) { registerException(e); - if (nullsMask == null) { - nullsMask = new BitSet(positionCount); - } - nullsMask.set(p); + builder.appendNull(); } } - return nullsMask == null - ? new BooleanArrayVector(values, positionCount).asBlock() - // UNORDERED, since whatever ordering there is, it isn't necessarily preserved - : new BooleanArrayBlock(values, positionCount, null, nullsMask, Block.MvOrdering.UNORDERED); + return builder.build(); } private static boolean evalValue(LongVector container, int index) { @@ -72,7 +66,7 @@ private static boolean evalValue(LongVector container, int index) { public Block evalBlock(Block b) { LongBlock block = (LongBlock) b; int positionCount = block.getPositionCount(); - BooleanBlock.Builder builder = BooleanBlock.newBlockBuilder(positionCount); + BooleanBlock.Builder builder = BooleanBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); for (int p = 0; p < positionCount; p++) { int valueCount = block.getValueCount(p); int start = block.getFirstValueIndex(p); diff --git a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToDatetimeFromStringEvaluator.java b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToDatetimeFromStringEvaluator.java index 98310bb390392..ca237c1dcc4a7 100644 --- a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToDatetimeFromStringEvaluator.java +++ b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToDatetimeFromStringEvaluator.java @@ -6,16 +6,13 @@ import java.lang.Override; import java.lang.String; -import java.util.BitSet; import org.apache.lucene.util.BytesRef; import org.elasticsearch.compute.data.Block; import org.elasticsearch.compute.data.BytesRefBlock; import org.elasticsearch.compute.data.BytesRefVector; -import org.elasticsearch.compute.data.ConstantLongVector; -import org.elasticsearch.compute.data.LongArrayBlock; -import org.elasticsearch.compute.data.LongArrayVector; import org.elasticsearch.compute.data.LongBlock; import org.elasticsearch.compute.data.Vector; +import org.elasticsearch.compute.operator.DriverContext; import org.elasticsearch.compute.operator.EvalOperator; import org.elasticsearch.xpack.ql.tree.Source; @@ -24,8 +21,12 @@ * This class is generated. Do not edit it. */ public final class ToDatetimeFromStringEvaluator extends AbstractConvertFunction.AbstractEvaluator { - public ToDatetimeFromStringEvaluator(EvalOperator.ExpressionEvaluator field, Source source) { + private final DriverContext driverContext; + + public ToDatetimeFromStringEvaluator(EvalOperator.ExpressionEvaluator field, Source source, + DriverContext driverContext) { super(field, source); + this.driverContext = driverContext; } @Override @@ -40,29 +41,22 @@ public Block evalVector(Vector v) { BytesRef scratchPad = new BytesRef(); if (vector.isConstant()) { try { - return new ConstantLongVector(evalValue(vector, 0, scratchPad), positionCount).asBlock(); + return driverContext.blockFactory().newConstantLongBlockWith(evalValue(vector, 0, scratchPad), positionCount); } catch (Exception e) { registerException(e); - return Block.constantNullBlock(positionCount); + return Block.constantNullBlock(positionCount, driverContext.blockFactory()); } } - BitSet nullsMask = null; - long[] values = new long[positionCount]; + LongBlock.Builder builder = LongBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); for (int p = 0; p < positionCount; p++) { try { - values[p] = evalValue(vector, p, scratchPad); + builder.appendLong(evalValue(vector, p, scratchPad)); } catch (Exception e) { registerException(e); - if (nullsMask == null) { - nullsMask = new BitSet(positionCount); - } - nullsMask.set(p); + builder.appendNull(); } } - return nullsMask == null - ? new LongArrayVector(values, positionCount).asBlock() - // UNORDERED, since whatever ordering there is, it isn't necessarily preserved - : new LongArrayBlock(values, positionCount, null, nullsMask, Block.MvOrdering.UNORDERED); + return builder.build(); } private static long evalValue(BytesRefVector container, int index, BytesRef scratchPad) { @@ -74,7 +68,7 @@ private static long evalValue(BytesRefVector container, int index, BytesRef scra public Block evalBlock(Block b) { BytesRefBlock block = (BytesRefBlock) b; int positionCount = block.getPositionCount(); - LongBlock.Builder builder = LongBlock.newBlockBuilder(positionCount); + LongBlock.Builder builder = LongBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); BytesRef scratchPad = new BytesRef(); for (int p = 0; p < positionCount; p++) { int valueCount = block.getValueCount(p); diff --git a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToDegreesEvaluator.java b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToDegreesEvaluator.java index a168d93e73ba3..27509a4a18e56 100644 --- a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToDegreesEvaluator.java +++ b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToDegreesEvaluator.java @@ -6,14 +6,11 @@ import java.lang.Override; import java.lang.String; -import java.util.BitSet; import org.elasticsearch.compute.data.Block; -import org.elasticsearch.compute.data.ConstantDoubleVector; -import org.elasticsearch.compute.data.DoubleArrayBlock; -import org.elasticsearch.compute.data.DoubleArrayVector; import org.elasticsearch.compute.data.DoubleBlock; import org.elasticsearch.compute.data.DoubleVector; import org.elasticsearch.compute.data.Vector; +import org.elasticsearch.compute.operator.DriverContext; import org.elasticsearch.compute.operator.EvalOperator; import org.elasticsearch.xpack.ql.tree.Source; @@ -22,8 +19,12 @@ * This class is generated. Do not edit it. */ public final class ToDegreesEvaluator extends AbstractConvertFunction.AbstractEvaluator { - public ToDegreesEvaluator(EvalOperator.ExpressionEvaluator field, Source source) { + private final DriverContext driverContext; + + public ToDegreesEvaluator(EvalOperator.ExpressionEvaluator field, Source source, + DriverContext driverContext) { super(field, source); + this.driverContext = driverContext; } @Override @@ -37,29 +38,22 @@ public Block evalVector(Vector v) { int positionCount = v.getPositionCount(); if (vector.isConstant()) { try { - return new ConstantDoubleVector(evalValue(vector, 0), positionCount).asBlock(); + return driverContext.blockFactory().newConstantDoubleBlockWith(evalValue(vector, 0), positionCount); } catch (Exception e) { registerException(e); - return Block.constantNullBlock(positionCount); + return Block.constantNullBlock(positionCount, driverContext.blockFactory()); } } - BitSet nullsMask = null; - double[] values = new double[positionCount]; + DoubleBlock.Builder builder = DoubleBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); for (int p = 0; p < positionCount; p++) { try { - values[p] = evalValue(vector, p); + builder.appendDouble(evalValue(vector, p)); } catch (Exception e) { registerException(e); - if (nullsMask == null) { - nullsMask = new BitSet(positionCount); - } - nullsMask.set(p); + builder.appendNull(); } } - return nullsMask == null - ? new DoubleArrayVector(values, positionCount).asBlock() - // UNORDERED, since whatever ordering there is, it isn't necessarily preserved - : new DoubleArrayBlock(values, positionCount, null, nullsMask, Block.MvOrdering.UNORDERED); + return builder.build(); } private static double evalValue(DoubleVector container, int index) { @@ -71,7 +65,7 @@ private static double evalValue(DoubleVector container, int index) { public Block evalBlock(Block b) { DoubleBlock block = (DoubleBlock) b; int positionCount = block.getPositionCount(); - DoubleBlock.Builder builder = DoubleBlock.newBlockBuilder(positionCount); + DoubleBlock.Builder builder = DoubleBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); for (int p = 0; p < positionCount; p++) { int valueCount = block.getValueCount(p); int start = block.getFirstValueIndex(p); diff --git a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToDoubleFromBooleanEvaluator.java b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToDoubleFromBooleanEvaluator.java index d2b16e4b722cb..a6ab12763ddc2 100644 --- a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToDoubleFromBooleanEvaluator.java +++ b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToDoubleFromBooleanEvaluator.java @@ -6,15 +6,12 @@ import java.lang.Override; import java.lang.String; -import java.util.BitSet; import org.elasticsearch.compute.data.Block; import org.elasticsearch.compute.data.BooleanBlock; import org.elasticsearch.compute.data.BooleanVector; -import org.elasticsearch.compute.data.ConstantDoubleVector; -import org.elasticsearch.compute.data.DoubleArrayBlock; -import org.elasticsearch.compute.data.DoubleArrayVector; import org.elasticsearch.compute.data.DoubleBlock; import org.elasticsearch.compute.data.Vector; +import org.elasticsearch.compute.operator.DriverContext; import org.elasticsearch.compute.operator.EvalOperator; import org.elasticsearch.xpack.ql.tree.Source; @@ -23,8 +20,12 @@ * This class is generated. Do not edit it. */ public final class ToDoubleFromBooleanEvaluator extends AbstractConvertFunction.AbstractEvaluator { - public ToDoubleFromBooleanEvaluator(EvalOperator.ExpressionEvaluator field, Source source) { + private final DriverContext driverContext; + + public ToDoubleFromBooleanEvaluator(EvalOperator.ExpressionEvaluator field, Source source, + DriverContext driverContext) { super(field, source); + this.driverContext = driverContext; } @Override @@ -38,29 +39,22 @@ public Block evalVector(Vector v) { int positionCount = v.getPositionCount(); if (vector.isConstant()) { try { - return new ConstantDoubleVector(evalValue(vector, 0), positionCount).asBlock(); + return driverContext.blockFactory().newConstantDoubleBlockWith(evalValue(vector, 0), positionCount); } catch (Exception e) { registerException(e); - return Block.constantNullBlock(positionCount); + return Block.constantNullBlock(positionCount, driverContext.blockFactory()); } } - BitSet nullsMask = null; - double[] values = new double[positionCount]; + DoubleBlock.Builder builder = DoubleBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); for (int p = 0; p < positionCount; p++) { try { - values[p] = evalValue(vector, p); + builder.appendDouble(evalValue(vector, p)); } catch (Exception e) { registerException(e); - if (nullsMask == null) { - nullsMask = new BitSet(positionCount); - } - nullsMask.set(p); + builder.appendNull(); } } - return nullsMask == null - ? new DoubleArrayVector(values, positionCount).asBlock() - // UNORDERED, since whatever ordering there is, it isn't necessarily preserved - : new DoubleArrayBlock(values, positionCount, null, nullsMask, Block.MvOrdering.UNORDERED); + return builder.build(); } private static double evalValue(BooleanVector container, int index) { @@ -72,7 +66,7 @@ private static double evalValue(BooleanVector container, int index) { public Block evalBlock(Block b) { BooleanBlock block = (BooleanBlock) b; int positionCount = block.getPositionCount(); - DoubleBlock.Builder builder = DoubleBlock.newBlockBuilder(positionCount); + DoubleBlock.Builder builder = DoubleBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); for (int p = 0; p < positionCount; p++) { int valueCount = block.getValueCount(p); int start = block.getFirstValueIndex(p); diff --git a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToDoubleFromIntEvaluator.java b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToDoubleFromIntEvaluator.java index 53e8edac3c5b3..5889cf151f0fa 100644 --- a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToDoubleFromIntEvaluator.java +++ b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToDoubleFromIntEvaluator.java @@ -6,15 +6,12 @@ import java.lang.Override; import java.lang.String; -import java.util.BitSet; import org.elasticsearch.compute.data.Block; -import org.elasticsearch.compute.data.ConstantDoubleVector; -import org.elasticsearch.compute.data.DoubleArrayBlock; -import org.elasticsearch.compute.data.DoubleArrayVector; import org.elasticsearch.compute.data.DoubleBlock; import org.elasticsearch.compute.data.IntBlock; import org.elasticsearch.compute.data.IntVector; import org.elasticsearch.compute.data.Vector; +import org.elasticsearch.compute.operator.DriverContext; import org.elasticsearch.compute.operator.EvalOperator; import org.elasticsearch.xpack.ql.tree.Source; @@ -23,8 +20,12 @@ * This class is generated. Do not edit it. */ public final class ToDoubleFromIntEvaluator extends AbstractConvertFunction.AbstractEvaluator { - public ToDoubleFromIntEvaluator(EvalOperator.ExpressionEvaluator field, Source source) { + private final DriverContext driverContext; + + public ToDoubleFromIntEvaluator(EvalOperator.ExpressionEvaluator field, Source source, + DriverContext driverContext) { super(field, source); + this.driverContext = driverContext; } @Override @@ -38,29 +39,22 @@ public Block evalVector(Vector v) { int positionCount = v.getPositionCount(); if (vector.isConstant()) { try { - return new ConstantDoubleVector(evalValue(vector, 0), positionCount).asBlock(); + return driverContext.blockFactory().newConstantDoubleBlockWith(evalValue(vector, 0), positionCount); } catch (Exception e) { registerException(e); - return Block.constantNullBlock(positionCount); + return Block.constantNullBlock(positionCount, driverContext.blockFactory()); } } - BitSet nullsMask = null; - double[] values = new double[positionCount]; + DoubleBlock.Builder builder = DoubleBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); for (int p = 0; p < positionCount; p++) { try { - values[p] = evalValue(vector, p); + builder.appendDouble(evalValue(vector, p)); } catch (Exception e) { registerException(e); - if (nullsMask == null) { - nullsMask = new BitSet(positionCount); - } - nullsMask.set(p); + builder.appendNull(); } } - return nullsMask == null - ? new DoubleArrayVector(values, positionCount).asBlock() - // UNORDERED, since whatever ordering there is, it isn't necessarily preserved - : new DoubleArrayBlock(values, positionCount, null, nullsMask, Block.MvOrdering.UNORDERED); + return builder.build(); } private static double evalValue(IntVector container, int index) { @@ -72,7 +66,7 @@ private static double evalValue(IntVector container, int index) { public Block evalBlock(Block b) { IntBlock block = (IntBlock) b; int positionCount = block.getPositionCount(); - DoubleBlock.Builder builder = DoubleBlock.newBlockBuilder(positionCount); + DoubleBlock.Builder builder = DoubleBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); for (int p = 0; p < positionCount; p++) { int valueCount = block.getValueCount(p); int start = block.getFirstValueIndex(p); diff --git a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToDoubleFromLongEvaluator.java b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToDoubleFromLongEvaluator.java index 9be5f1f2456b1..ff1c81f3f544f 100644 --- a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToDoubleFromLongEvaluator.java +++ b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToDoubleFromLongEvaluator.java @@ -6,15 +6,12 @@ import java.lang.Override; import java.lang.String; -import java.util.BitSet; import org.elasticsearch.compute.data.Block; -import org.elasticsearch.compute.data.ConstantDoubleVector; -import org.elasticsearch.compute.data.DoubleArrayBlock; -import org.elasticsearch.compute.data.DoubleArrayVector; import org.elasticsearch.compute.data.DoubleBlock; import org.elasticsearch.compute.data.LongBlock; import org.elasticsearch.compute.data.LongVector; import org.elasticsearch.compute.data.Vector; +import org.elasticsearch.compute.operator.DriverContext; import org.elasticsearch.compute.operator.EvalOperator; import org.elasticsearch.xpack.ql.tree.Source; @@ -23,8 +20,12 @@ * This class is generated. Do not edit it. */ public final class ToDoubleFromLongEvaluator extends AbstractConvertFunction.AbstractEvaluator { - public ToDoubleFromLongEvaluator(EvalOperator.ExpressionEvaluator field, Source source) { + private final DriverContext driverContext; + + public ToDoubleFromLongEvaluator(EvalOperator.ExpressionEvaluator field, Source source, + DriverContext driverContext) { super(field, source); + this.driverContext = driverContext; } @Override @@ -38,29 +39,22 @@ public Block evalVector(Vector v) { int positionCount = v.getPositionCount(); if (vector.isConstant()) { try { - return new ConstantDoubleVector(evalValue(vector, 0), positionCount).asBlock(); + return driverContext.blockFactory().newConstantDoubleBlockWith(evalValue(vector, 0), positionCount); } catch (Exception e) { registerException(e); - return Block.constantNullBlock(positionCount); + return Block.constantNullBlock(positionCount, driverContext.blockFactory()); } } - BitSet nullsMask = null; - double[] values = new double[positionCount]; + DoubleBlock.Builder builder = DoubleBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); for (int p = 0; p < positionCount; p++) { try { - values[p] = evalValue(vector, p); + builder.appendDouble(evalValue(vector, p)); } catch (Exception e) { registerException(e); - if (nullsMask == null) { - nullsMask = new BitSet(positionCount); - } - nullsMask.set(p); + builder.appendNull(); } } - return nullsMask == null - ? new DoubleArrayVector(values, positionCount).asBlock() - // UNORDERED, since whatever ordering there is, it isn't necessarily preserved - : new DoubleArrayBlock(values, positionCount, null, nullsMask, Block.MvOrdering.UNORDERED); + return builder.build(); } private static double evalValue(LongVector container, int index) { @@ -72,7 +66,7 @@ private static double evalValue(LongVector container, int index) { public Block evalBlock(Block b) { LongBlock block = (LongBlock) b; int positionCount = block.getPositionCount(); - DoubleBlock.Builder builder = DoubleBlock.newBlockBuilder(positionCount); + DoubleBlock.Builder builder = DoubleBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); for (int p = 0; p < positionCount; p++) { int valueCount = block.getValueCount(p); int start = block.getFirstValueIndex(p); diff --git a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToDoubleFromStringEvaluator.java b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToDoubleFromStringEvaluator.java index 653034f0c3bc9..197e5e5f2db36 100644 --- a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToDoubleFromStringEvaluator.java +++ b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToDoubleFromStringEvaluator.java @@ -6,16 +6,13 @@ import java.lang.Override; import java.lang.String; -import java.util.BitSet; import org.apache.lucene.util.BytesRef; import org.elasticsearch.compute.data.Block; import org.elasticsearch.compute.data.BytesRefBlock; import org.elasticsearch.compute.data.BytesRefVector; -import org.elasticsearch.compute.data.ConstantDoubleVector; -import org.elasticsearch.compute.data.DoubleArrayBlock; -import org.elasticsearch.compute.data.DoubleArrayVector; import org.elasticsearch.compute.data.DoubleBlock; import org.elasticsearch.compute.data.Vector; +import org.elasticsearch.compute.operator.DriverContext; import org.elasticsearch.compute.operator.EvalOperator; import org.elasticsearch.xpack.ql.tree.Source; @@ -24,8 +21,12 @@ * This class is generated. Do not edit it. */ public final class ToDoubleFromStringEvaluator extends AbstractConvertFunction.AbstractEvaluator { - public ToDoubleFromStringEvaluator(EvalOperator.ExpressionEvaluator field, Source source) { + private final DriverContext driverContext; + + public ToDoubleFromStringEvaluator(EvalOperator.ExpressionEvaluator field, Source source, + DriverContext driverContext) { super(field, source); + this.driverContext = driverContext; } @Override @@ -40,29 +41,22 @@ public Block evalVector(Vector v) { BytesRef scratchPad = new BytesRef(); if (vector.isConstant()) { try { - return new ConstantDoubleVector(evalValue(vector, 0, scratchPad), positionCount).asBlock(); + return driverContext.blockFactory().newConstantDoubleBlockWith(evalValue(vector, 0, scratchPad), positionCount); } catch (Exception e) { registerException(e); - return Block.constantNullBlock(positionCount); + return Block.constantNullBlock(positionCount, driverContext.blockFactory()); } } - BitSet nullsMask = null; - double[] values = new double[positionCount]; + DoubleBlock.Builder builder = DoubleBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); for (int p = 0; p < positionCount; p++) { try { - values[p] = evalValue(vector, p, scratchPad); + builder.appendDouble(evalValue(vector, p, scratchPad)); } catch (Exception e) { registerException(e); - if (nullsMask == null) { - nullsMask = new BitSet(positionCount); - } - nullsMask.set(p); + builder.appendNull(); } } - return nullsMask == null - ? new DoubleArrayVector(values, positionCount).asBlock() - // UNORDERED, since whatever ordering there is, it isn't necessarily preserved - : new DoubleArrayBlock(values, positionCount, null, nullsMask, Block.MvOrdering.UNORDERED); + return builder.build(); } private static double evalValue(BytesRefVector container, int index, BytesRef scratchPad) { @@ -74,7 +68,7 @@ private static double evalValue(BytesRefVector container, int index, BytesRef sc public Block evalBlock(Block b) { BytesRefBlock block = (BytesRefBlock) b; int positionCount = block.getPositionCount(); - DoubleBlock.Builder builder = DoubleBlock.newBlockBuilder(positionCount); + DoubleBlock.Builder builder = DoubleBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); BytesRef scratchPad = new BytesRef(); for (int p = 0; p < positionCount; p++) { int valueCount = block.getValueCount(p); diff --git a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToDoubleFromUnsignedLongEvaluator.java b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToDoubleFromUnsignedLongEvaluator.java index 54cc374c758fb..018517ae61d36 100644 --- a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToDoubleFromUnsignedLongEvaluator.java +++ b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToDoubleFromUnsignedLongEvaluator.java @@ -6,15 +6,12 @@ import java.lang.Override; import java.lang.String; -import java.util.BitSet; import org.elasticsearch.compute.data.Block; -import org.elasticsearch.compute.data.ConstantDoubleVector; -import org.elasticsearch.compute.data.DoubleArrayBlock; -import org.elasticsearch.compute.data.DoubleArrayVector; import org.elasticsearch.compute.data.DoubleBlock; import org.elasticsearch.compute.data.LongBlock; import org.elasticsearch.compute.data.LongVector; import org.elasticsearch.compute.data.Vector; +import org.elasticsearch.compute.operator.DriverContext; import org.elasticsearch.compute.operator.EvalOperator; import org.elasticsearch.xpack.ql.tree.Source; @@ -23,8 +20,12 @@ * This class is generated. Do not edit it. */ public final class ToDoubleFromUnsignedLongEvaluator extends AbstractConvertFunction.AbstractEvaluator { - public ToDoubleFromUnsignedLongEvaluator(EvalOperator.ExpressionEvaluator field, Source source) { + private final DriverContext driverContext; + + public ToDoubleFromUnsignedLongEvaluator(EvalOperator.ExpressionEvaluator field, Source source, + DriverContext driverContext) { super(field, source); + this.driverContext = driverContext; } @Override @@ -38,29 +39,22 @@ public Block evalVector(Vector v) { int positionCount = v.getPositionCount(); if (vector.isConstant()) { try { - return new ConstantDoubleVector(evalValue(vector, 0), positionCount).asBlock(); + return driverContext.blockFactory().newConstantDoubleBlockWith(evalValue(vector, 0), positionCount); } catch (Exception e) { registerException(e); - return Block.constantNullBlock(positionCount); + return Block.constantNullBlock(positionCount, driverContext.blockFactory()); } } - BitSet nullsMask = null; - double[] values = new double[positionCount]; + DoubleBlock.Builder builder = DoubleBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); for (int p = 0; p < positionCount; p++) { try { - values[p] = evalValue(vector, p); + builder.appendDouble(evalValue(vector, p)); } catch (Exception e) { registerException(e); - if (nullsMask == null) { - nullsMask = new BitSet(positionCount); - } - nullsMask.set(p); + builder.appendNull(); } } - return nullsMask == null - ? new DoubleArrayVector(values, positionCount).asBlock() - // UNORDERED, since whatever ordering there is, it isn't necessarily preserved - : new DoubleArrayBlock(values, positionCount, null, nullsMask, Block.MvOrdering.UNORDERED); + return builder.build(); } private static double evalValue(LongVector container, int index) { @@ -72,7 +66,7 @@ private static double evalValue(LongVector container, int index) { public Block evalBlock(Block b) { LongBlock block = (LongBlock) b; int positionCount = block.getPositionCount(); - DoubleBlock.Builder builder = DoubleBlock.newBlockBuilder(positionCount); + DoubleBlock.Builder builder = DoubleBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); for (int p = 0; p < positionCount; p++) { int valueCount = block.getValueCount(p); int start = block.getFirstValueIndex(p); diff --git a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToIPFromStringEvaluator.java b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToIPFromStringEvaluator.java index 76d5c58961970..b62fa771e492c 100644 --- a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToIPFromStringEvaluator.java +++ b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToIPFromStringEvaluator.java @@ -6,17 +6,12 @@ import java.lang.Override; import java.lang.String; -import java.util.BitSet; import org.apache.lucene.util.BytesRef; -import org.elasticsearch.common.util.BigArrays; -import org.elasticsearch.common.util.BytesRefArray; import org.elasticsearch.compute.data.Block; -import org.elasticsearch.compute.data.BytesRefArrayBlock; -import org.elasticsearch.compute.data.BytesRefArrayVector; import org.elasticsearch.compute.data.BytesRefBlock; import org.elasticsearch.compute.data.BytesRefVector; -import org.elasticsearch.compute.data.ConstantBytesRefVector; import org.elasticsearch.compute.data.Vector; +import org.elasticsearch.compute.operator.DriverContext; import org.elasticsearch.compute.operator.EvalOperator; import org.elasticsearch.xpack.ql.tree.Source; @@ -25,8 +20,12 @@ * This class is generated. Do not edit it. */ public final class ToIPFromStringEvaluator extends AbstractConvertFunction.AbstractEvaluator { - public ToIPFromStringEvaluator(EvalOperator.ExpressionEvaluator field, Source source) { + private final DriverContext driverContext; + + public ToIPFromStringEvaluator(EvalOperator.ExpressionEvaluator field, Source source, + DriverContext driverContext) { super(field, source); + this.driverContext = driverContext; } @Override @@ -41,30 +40,22 @@ public Block evalVector(Vector v) { BytesRef scratchPad = new BytesRef(); if (vector.isConstant()) { try { - return new ConstantBytesRefVector(evalValue(vector, 0, scratchPad), positionCount).asBlock(); + return driverContext.blockFactory().newConstantBytesRefBlockWith(evalValue(vector, 0, scratchPad), positionCount); } catch (Exception e) { registerException(e); - return Block.constantNullBlock(positionCount); + return Block.constantNullBlock(positionCount, driverContext.blockFactory()); } } - BitSet nullsMask = null; - BytesRefArray values = new BytesRefArray(positionCount, BigArrays.NON_RECYCLING_INSTANCE); + BytesRefBlock.Builder builder = BytesRefBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); for (int p = 0; p < positionCount; p++) { try { - values.append(evalValue(vector, p, scratchPad)); + builder.appendBytesRef(evalValue(vector, p, scratchPad)); } catch (Exception e) { registerException(e); - if (nullsMask == null) { - nullsMask = new BitSet(positionCount); - } - nullsMask.set(p); - values.append(BytesRefBlock.NULL_VALUE); + builder.appendNull(); } } - return nullsMask == null - ? new BytesRefArrayVector(values, positionCount).asBlock() - // UNORDERED, since whatever ordering there is, it isn't necessarily preserved - : new BytesRefArrayBlock(values, positionCount, null, nullsMask, Block.MvOrdering.UNORDERED); + return builder.build(); } private static BytesRef evalValue(BytesRefVector container, int index, BytesRef scratchPad) { @@ -76,7 +67,7 @@ private static BytesRef evalValue(BytesRefVector container, int index, BytesRef public Block evalBlock(Block b) { BytesRefBlock block = (BytesRefBlock) b; int positionCount = block.getPositionCount(); - BytesRefBlock.Builder builder = BytesRefBlock.newBlockBuilder(positionCount); + BytesRefBlock.Builder builder = BytesRefBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); BytesRef scratchPad = new BytesRef(); for (int p = 0; p < positionCount; p++) { int valueCount = block.getValueCount(p); diff --git a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToIntegerFromBooleanEvaluator.java b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToIntegerFromBooleanEvaluator.java index 49f79cd0bcd3e..9529769a02200 100644 --- a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToIntegerFromBooleanEvaluator.java +++ b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToIntegerFromBooleanEvaluator.java @@ -6,15 +6,12 @@ import java.lang.Override; import java.lang.String; -import java.util.BitSet; import org.elasticsearch.compute.data.Block; import org.elasticsearch.compute.data.BooleanBlock; import org.elasticsearch.compute.data.BooleanVector; -import org.elasticsearch.compute.data.ConstantIntVector; -import org.elasticsearch.compute.data.IntArrayBlock; -import org.elasticsearch.compute.data.IntArrayVector; import org.elasticsearch.compute.data.IntBlock; import org.elasticsearch.compute.data.Vector; +import org.elasticsearch.compute.operator.DriverContext; import org.elasticsearch.compute.operator.EvalOperator; import org.elasticsearch.xpack.ql.tree.Source; @@ -23,8 +20,12 @@ * This class is generated. Do not edit it. */ public final class ToIntegerFromBooleanEvaluator extends AbstractConvertFunction.AbstractEvaluator { - public ToIntegerFromBooleanEvaluator(EvalOperator.ExpressionEvaluator field, Source source) { + private final DriverContext driverContext; + + public ToIntegerFromBooleanEvaluator(EvalOperator.ExpressionEvaluator field, Source source, + DriverContext driverContext) { super(field, source); + this.driverContext = driverContext; } @Override @@ -38,29 +39,22 @@ public Block evalVector(Vector v) { int positionCount = v.getPositionCount(); if (vector.isConstant()) { try { - return new ConstantIntVector(evalValue(vector, 0), positionCount).asBlock(); + return driverContext.blockFactory().newConstantIntBlockWith(evalValue(vector, 0), positionCount); } catch (Exception e) { registerException(e); - return Block.constantNullBlock(positionCount); + return Block.constantNullBlock(positionCount, driverContext.blockFactory()); } } - BitSet nullsMask = null; - int[] values = new int[positionCount]; + IntBlock.Builder builder = IntBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); for (int p = 0; p < positionCount; p++) { try { - values[p] = evalValue(vector, p); + builder.appendInt(evalValue(vector, p)); } catch (Exception e) { registerException(e); - if (nullsMask == null) { - nullsMask = new BitSet(positionCount); - } - nullsMask.set(p); + builder.appendNull(); } } - return nullsMask == null - ? new IntArrayVector(values, positionCount).asBlock() - // UNORDERED, since whatever ordering there is, it isn't necessarily preserved - : new IntArrayBlock(values, positionCount, null, nullsMask, Block.MvOrdering.UNORDERED); + return builder.build(); } private static int evalValue(BooleanVector container, int index) { @@ -72,7 +66,7 @@ private static int evalValue(BooleanVector container, int index) { public Block evalBlock(Block b) { BooleanBlock block = (BooleanBlock) b; int positionCount = block.getPositionCount(); - IntBlock.Builder builder = IntBlock.newBlockBuilder(positionCount); + IntBlock.Builder builder = IntBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); for (int p = 0; p < positionCount; p++) { int valueCount = block.getValueCount(p); int start = block.getFirstValueIndex(p); diff --git a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToIntegerFromDoubleEvaluator.java b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToIntegerFromDoubleEvaluator.java index e1b0db72ad7d9..7af8bdbf083ef 100644 --- a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToIntegerFromDoubleEvaluator.java +++ b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToIntegerFromDoubleEvaluator.java @@ -6,15 +6,12 @@ import java.lang.Override; import java.lang.String; -import java.util.BitSet; import org.elasticsearch.compute.data.Block; -import org.elasticsearch.compute.data.ConstantIntVector; import org.elasticsearch.compute.data.DoubleBlock; import org.elasticsearch.compute.data.DoubleVector; -import org.elasticsearch.compute.data.IntArrayBlock; -import org.elasticsearch.compute.data.IntArrayVector; import org.elasticsearch.compute.data.IntBlock; import org.elasticsearch.compute.data.Vector; +import org.elasticsearch.compute.operator.DriverContext; import org.elasticsearch.compute.operator.EvalOperator; import org.elasticsearch.xpack.ql.tree.Source; @@ -23,8 +20,12 @@ * This class is generated. Do not edit it. */ public final class ToIntegerFromDoubleEvaluator extends AbstractConvertFunction.AbstractEvaluator { - public ToIntegerFromDoubleEvaluator(EvalOperator.ExpressionEvaluator field, Source source) { + private final DriverContext driverContext; + + public ToIntegerFromDoubleEvaluator(EvalOperator.ExpressionEvaluator field, Source source, + DriverContext driverContext) { super(field, source); + this.driverContext = driverContext; } @Override @@ -38,29 +39,22 @@ public Block evalVector(Vector v) { int positionCount = v.getPositionCount(); if (vector.isConstant()) { try { - return new ConstantIntVector(evalValue(vector, 0), positionCount).asBlock(); + return driverContext.blockFactory().newConstantIntBlockWith(evalValue(vector, 0), positionCount); } catch (Exception e) { registerException(e); - return Block.constantNullBlock(positionCount); + return Block.constantNullBlock(positionCount, driverContext.blockFactory()); } } - BitSet nullsMask = null; - int[] values = new int[positionCount]; + IntBlock.Builder builder = IntBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); for (int p = 0; p < positionCount; p++) { try { - values[p] = evalValue(vector, p); + builder.appendInt(evalValue(vector, p)); } catch (Exception e) { registerException(e); - if (nullsMask == null) { - nullsMask = new BitSet(positionCount); - } - nullsMask.set(p); + builder.appendNull(); } } - return nullsMask == null - ? new IntArrayVector(values, positionCount).asBlock() - // UNORDERED, since whatever ordering there is, it isn't necessarily preserved - : new IntArrayBlock(values, positionCount, null, nullsMask, Block.MvOrdering.UNORDERED); + return builder.build(); } private static int evalValue(DoubleVector container, int index) { @@ -72,7 +66,7 @@ private static int evalValue(DoubleVector container, int index) { public Block evalBlock(Block b) { DoubleBlock block = (DoubleBlock) b; int positionCount = block.getPositionCount(); - IntBlock.Builder builder = IntBlock.newBlockBuilder(positionCount); + IntBlock.Builder builder = IntBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); for (int p = 0; p < positionCount; p++) { int valueCount = block.getValueCount(p); int start = block.getFirstValueIndex(p); diff --git a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToIntegerFromLongEvaluator.java b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToIntegerFromLongEvaluator.java index 9a1394b9c02cf..a84367ab27a30 100644 --- a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToIntegerFromLongEvaluator.java +++ b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToIntegerFromLongEvaluator.java @@ -6,15 +6,12 @@ import java.lang.Override; import java.lang.String; -import java.util.BitSet; import org.elasticsearch.compute.data.Block; -import org.elasticsearch.compute.data.ConstantIntVector; -import org.elasticsearch.compute.data.IntArrayBlock; -import org.elasticsearch.compute.data.IntArrayVector; import org.elasticsearch.compute.data.IntBlock; import org.elasticsearch.compute.data.LongBlock; import org.elasticsearch.compute.data.LongVector; import org.elasticsearch.compute.data.Vector; +import org.elasticsearch.compute.operator.DriverContext; import org.elasticsearch.compute.operator.EvalOperator; import org.elasticsearch.xpack.ql.tree.Source; @@ -23,8 +20,12 @@ * This class is generated. Do not edit it. */ public final class ToIntegerFromLongEvaluator extends AbstractConvertFunction.AbstractEvaluator { - public ToIntegerFromLongEvaluator(EvalOperator.ExpressionEvaluator field, Source source) { + private final DriverContext driverContext; + + public ToIntegerFromLongEvaluator(EvalOperator.ExpressionEvaluator field, Source source, + DriverContext driverContext) { super(field, source); + this.driverContext = driverContext; } @Override @@ -38,29 +39,22 @@ public Block evalVector(Vector v) { int positionCount = v.getPositionCount(); if (vector.isConstant()) { try { - return new ConstantIntVector(evalValue(vector, 0), positionCount).asBlock(); + return driverContext.blockFactory().newConstantIntBlockWith(evalValue(vector, 0), positionCount); } catch (Exception e) { registerException(e); - return Block.constantNullBlock(positionCount); + return Block.constantNullBlock(positionCount, driverContext.blockFactory()); } } - BitSet nullsMask = null; - int[] values = new int[positionCount]; + IntBlock.Builder builder = IntBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); for (int p = 0; p < positionCount; p++) { try { - values[p] = evalValue(vector, p); + builder.appendInt(evalValue(vector, p)); } catch (Exception e) { registerException(e); - if (nullsMask == null) { - nullsMask = new BitSet(positionCount); - } - nullsMask.set(p); + builder.appendNull(); } } - return nullsMask == null - ? new IntArrayVector(values, positionCount).asBlock() - // UNORDERED, since whatever ordering there is, it isn't necessarily preserved - : new IntArrayBlock(values, positionCount, null, nullsMask, Block.MvOrdering.UNORDERED); + return builder.build(); } private static int evalValue(LongVector container, int index) { @@ -72,7 +66,7 @@ private static int evalValue(LongVector container, int index) { public Block evalBlock(Block b) { LongBlock block = (LongBlock) b; int positionCount = block.getPositionCount(); - IntBlock.Builder builder = IntBlock.newBlockBuilder(positionCount); + IntBlock.Builder builder = IntBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); for (int p = 0; p < positionCount; p++) { int valueCount = block.getValueCount(p); int start = block.getFirstValueIndex(p); diff --git a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToIntegerFromStringEvaluator.java b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToIntegerFromStringEvaluator.java index 180e64f97e63b..bd7085764e341 100644 --- a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToIntegerFromStringEvaluator.java +++ b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToIntegerFromStringEvaluator.java @@ -6,16 +6,13 @@ import java.lang.Override; import java.lang.String; -import java.util.BitSet; import org.apache.lucene.util.BytesRef; import org.elasticsearch.compute.data.Block; import org.elasticsearch.compute.data.BytesRefBlock; import org.elasticsearch.compute.data.BytesRefVector; -import org.elasticsearch.compute.data.ConstantIntVector; -import org.elasticsearch.compute.data.IntArrayBlock; -import org.elasticsearch.compute.data.IntArrayVector; import org.elasticsearch.compute.data.IntBlock; import org.elasticsearch.compute.data.Vector; +import org.elasticsearch.compute.operator.DriverContext; import org.elasticsearch.compute.operator.EvalOperator; import org.elasticsearch.xpack.ql.tree.Source; @@ -24,8 +21,12 @@ * This class is generated. Do not edit it. */ public final class ToIntegerFromStringEvaluator extends AbstractConvertFunction.AbstractEvaluator { - public ToIntegerFromStringEvaluator(EvalOperator.ExpressionEvaluator field, Source source) { + private final DriverContext driverContext; + + public ToIntegerFromStringEvaluator(EvalOperator.ExpressionEvaluator field, Source source, + DriverContext driverContext) { super(field, source); + this.driverContext = driverContext; } @Override @@ -40,29 +41,22 @@ public Block evalVector(Vector v) { BytesRef scratchPad = new BytesRef(); if (vector.isConstant()) { try { - return new ConstantIntVector(evalValue(vector, 0, scratchPad), positionCount).asBlock(); + return driverContext.blockFactory().newConstantIntBlockWith(evalValue(vector, 0, scratchPad), positionCount); } catch (Exception e) { registerException(e); - return Block.constantNullBlock(positionCount); + return Block.constantNullBlock(positionCount, driverContext.blockFactory()); } } - BitSet nullsMask = null; - int[] values = new int[positionCount]; + IntBlock.Builder builder = IntBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); for (int p = 0; p < positionCount; p++) { try { - values[p] = evalValue(vector, p, scratchPad); + builder.appendInt(evalValue(vector, p, scratchPad)); } catch (Exception e) { registerException(e); - if (nullsMask == null) { - nullsMask = new BitSet(positionCount); - } - nullsMask.set(p); + builder.appendNull(); } } - return nullsMask == null - ? new IntArrayVector(values, positionCount).asBlock() - // UNORDERED, since whatever ordering there is, it isn't necessarily preserved - : new IntArrayBlock(values, positionCount, null, nullsMask, Block.MvOrdering.UNORDERED); + return builder.build(); } private static int evalValue(BytesRefVector container, int index, BytesRef scratchPad) { @@ -74,7 +68,7 @@ private static int evalValue(BytesRefVector container, int index, BytesRef scrat public Block evalBlock(Block b) { BytesRefBlock block = (BytesRefBlock) b; int positionCount = block.getPositionCount(); - IntBlock.Builder builder = IntBlock.newBlockBuilder(positionCount); + IntBlock.Builder builder = IntBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); BytesRef scratchPad = new BytesRef(); for (int p = 0; p < positionCount; p++) { int valueCount = block.getValueCount(p); diff --git a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToIntegerFromUnsignedLongEvaluator.java b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToIntegerFromUnsignedLongEvaluator.java index 698db22c0ecc6..2312f94fec83e 100644 --- a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToIntegerFromUnsignedLongEvaluator.java +++ b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToIntegerFromUnsignedLongEvaluator.java @@ -6,15 +6,12 @@ import java.lang.Override; import java.lang.String; -import java.util.BitSet; import org.elasticsearch.compute.data.Block; -import org.elasticsearch.compute.data.ConstantIntVector; -import org.elasticsearch.compute.data.IntArrayBlock; -import org.elasticsearch.compute.data.IntArrayVector; import org.elasticsearch.compute.data.IntBlock; import org.elasticsearch.compute.data.LongBlock; import org.elasticsearch.compute.data.LongVector; import org.elasticsearch.compute.data.Vector; +import org.elasticsearch.compute.operator.DriverContext; import org.elasticsearch.compute.operator.EvalOperator; import org.elasticsearch.xpack.ql.tree.Source; @@ -23,8 +20,12 @@ * This class is generated. Do not edit it. */ public final class ToIntegerFromUnsignedLongEvaluator extends AbstractConvertFunction.AbstractEvaluator { - public ToIntegerFromUnsignedLongEvaluator(EvalOperator.ExpressionEvaluator field, Source source) { + private final DriverContext driverContext; + + public ToIntegerFromUnsignedLongEvaluator(EvalOperator.ExpressionEvaluator field, Source source, + DriverContext driverContext) { super(field, source); + this.driverContext = driverContext; } @Override @@ -38,29 +39,22 @@ public Block evalVector(Vector v) { int positionCount = v.getPositionCount(); if (vector.isConstant()) { try { - return new ConstantIntVector(evalValue(vector, 0), positionCount).asBlock(); + return driverContext.blockFactory().newConstantIntBlockWith(evalValue(vector, 0), positionCount); } catch (Exception e) { registerException(e); - return Block.constantNullBlock(positionCount); + return Block.constantNullBlock(positionCount, driverContext.blockFactory()); } } - BitSet nullsMask = null; - int[] values = new int[positionCount]; + IntBlock.Builder builder = IntBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); for (int p = 0; p < positionCount; p++) { try { - values[p] = evalValue(vector, p); + builder.appendInt(evalValue(vector, p)); } catch (Exception e) { registerException(e); - if (nullsMask == null) { - nullsMask = new BitSet(positionCount); - } - nullsMask.set(p); + builder.appendNull(); } } - return nullsMask == null - ? new IntArrayVector(values, positionCount).asBlock() - // UNORDERED, since whatever ordering there is, it isn't necessarily preserved - : new IntArrayBlock(values, positionCount, null, nullsMask, Block.MvOrdering.UNORDERED); + return builder.build(); } private static int evalValue(LongVector container, int index) { @@ -72,7 +66,7 @@ private static int evalValue(LongVector container, int index) { public Block evalBlock(Block b) { LongBlock block = (LongBlock) b; int positionCount = block.getPositionCount(); - IntBlock.Builder builder = IntBlock.newBlockBuilder(positionCount); + IntBlock.Builder builder = IntBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); for (int p = 0; p < positionCount; p++) { int valueCount = block.getValueCount(p); int start = block.getFirstValueIndex(p); diff --git a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToLongFromBooleanEvaluator.java b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToLongFromBooleanEvaluator.java index bf76fb0eb8a59..48e3e45d42f46 100644 --- a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToLongFromBooleanEvaluator.java +++ b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToLongFromBooleanEvaluator.java @@ -6,15 +6,12 @@ import java.lang.Override; import java.lang.String; -import java.util.BitSet; import org.elasticsearch.compute.data.Block; import org.elasticsearch.compute.data.BooleanBlock; import org.elasticsearch.compute.data.BooleanVector; -import org.elasticsearch.compute.data.ConstantLongVector; -import org.elasticsearch.compute.data.LongArrayBlock; -import org.elasticsearch.compute.data.LongArrayVector; import org.elasticsearch.compute.data.LongBlock; import org.elasticsearch.compute.data.Vector; +import org.elasticsearch.compute.operator.DriverContext; import org.elasticsearch.compute.operator.EvalOperator; import org.elasticsearch.xpack.ql.tree.Source; @@ -23,8 +20,12 @@ * This class is generated. Do not edit it. */ public final class ToLongFromBooleanEvaluator extends AbstractConvertFunction.AbstractEvaluator { - public ToLongFromBooleanEvaluator(EvalOperator.ExpressionEvaluator field, Source source) { + private final DriverContext driverContext; + + public ToLongFromBooleanEvaluator(EvalOperator.ExpressionEvaluator field, Source source, + DriverContext driverContext) { super(field, source); + this.driverContext = driverContext; } @Override @@ -38,29 +39,22 @@ public Block evalVector(Vector v) { int positionCount = v.getPositionCount(); if (vector.isConstant()) { try { - return new ConstantLongVector(evalValue(vector, 0), positionCount).asBlock(); + return driverContext.blockFactory().newConstantLongBlockWith(evalValue(vector, 0), positionCount); } catch (Exception e) { registerException(e); - return Block.constantNullBlock(positionCount); + return Block.constantNullBlock(positionCount, driverContext.blockFactory()); } } - BitSet nullsMask = null; - long[] values = new long[positionCount]; + LongBlock.Builder builder = LongBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); for (int p = 0; p < positionCount; p++) { try { - values[p] = evalValue(vector, p); + builder.appendLong(evalValue(vector, p)); } catch (Exception e) { registerException(e); - if (nullsMask == null) { - nullsMask = new BitSet(positionCount); - } - nullsMask.set(p); + builder.appendNull(); } } - return nullsMask == null - ? new LongArrayVector(values, positionCount).asBlock() - // UNORDERED, since whatever ordering there is, it isn't necessarily preserved - : new LongArrayBlock(values, positionCount, null, nullsMask, Block.MvOrdering.UNORDERED); + return builder.build(); } private static long evalValue(BooleanVector container, int index) { @@ -72,7 +66,7 @@ private static long evalValue(BooleanVector container, int index) { public Block evalBlock(Block b) { BooleanBlock block = (BooleanBlock) b; int positionCount = block.getPositionCount(); - LongBlock.Builder builder = LongBlock.newBlockBuilder(positionCount); + LongBlock.Builder builder = LongBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); for (int p = 0; p < positionCount; p++) { int valueCount = block.getValueCount(p); int start = block.getFirstValueIndex(p); diff --git a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToLongFromDoubleEvaluator.java b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToLongFromDoubleEvaluator.java index 116be245e3191..14ec5e41a04e5 100644 --- a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToLongFromDoubleEvaluator.java +++ b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToLongFromDoubleEvaluator.java @@ -6,15 +6,12 @@ import java.lang.Override; import java.lang.String; -import java.util.BitSet; import org.elasticsearch.compute.data.Block; -import org.elasticsearch.compute.data.ConstantLongVector; import org.elasticsearch.compute.data.DoubleBlock; import org.elasticsearch.compute.data.DoubleVector; -import org.elasticsearch.compute.data.LongArrayBlock; -import org.elasticsearch.compute.data.LongArrayVector; import org.elasticsearch.compute.data.LongBlock; import org.elasticsearch.compute.data.Vector; +import org.elasticsearch.compute.operator.DriverContext; import org.elasticsearch.compute.operator.EvalOperator; import org.elasticsearch.xpack.ql.tree.Source; @@ -23,8 +20,12 @@ * This class is generated. Do not edit it. */ public final class ToLongFromDoubleEvaluator extends AbstractConvertFunction.AbstractEvaluator { - public ToLongFromDoubleEvaluator(EvalOperator.ExpressionEvaluator field, Source source) { + private final DriverContext driverContext; + + public ToLongFromDoubleEvaluator(EvalOperator.ExpressionEvaluator field, Source source, + DriverContext driverContext) { super(field, source); + this.driverContext = driverContext; } @Override @@ -38,29 +39,22 @@ public Block evalVector(Vector v) { int positionCount = v.getPositionCount(); if (vector.isConstant()) { try { - return new ConstantLongVector(evalValue(vector, 0), positionCount).asBlock(); + return driverContext.blockFactory().newConstantLongBlockWith(evalValue(vector, 0), positionCount); } catch (Exception e) { registerException(e); - return Block.constantNullBlock(positionCount); + return Block.constantNullBlock(positionCount, driverContext.blockFactory()); } } - BitSet nullsMask = null; - long[] values = new long[positionCount]; + LongBlock.Builder builder = LongBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); for (int p = 0; p < positionCount; p++) { try { - values[p] = evalValue(vector, p); + builder.appendLong(evalValue(vector, p)); } catch (Exception e) { registerException(e); - if (nullsMask == null) { - nullsMask = new BitSet(positionCount); - } - nullsMask.set(p); + builder.appendNull(); } } - return nullsMask == null - ? new LongArrayVector(values, positionCount).asBlock() - // UNORDERED, since whatever ordering there is, it isn't necessarily preserved - : new LongArrayBlock(values, positionCount, null, nullsMask, Block.MvOrdering.UNORDERED); + return builder.build(); } private static long evalValue(DoubleVector container, int index) { @@ -72,7 +66,7 @@ private static long evalValue(DoubleVector container, int index) { public Block evalBlock(Block b) { DoubleBlock block = (DoubleBlock) b; int positionCount = block.getPositionCount(); - LongBlock.Builder builder = LongBlock.newBlockBuilder(positionCount); + LongBlock.Builder builder = LongBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); for (int p = 0; p < positionCount; p++) { int valueCount = block.getValueCount(p); int start = block.getFirstValueIndex(p); diff --git a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToLongFromIntEvaluator.java b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToLongFromIntEvaluator.java index 02d043c641cb0..f0eae8bfccc44 100644 --- a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToLongFromIntEvaluator.java +++ b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToLongFromIntEvaluator.java @@ -6,15 +6,12 @@ import java.lang.Override; import java.lang.String; -import java.util.BitSet; import org.elasticsearch.compute.data.Block; -import org.elasticsearch.compute.data.ConstantLongVector; import org.elasticsearch.compute.data.IntBlock; import org.elasticsearch.compute.data.IntVector; -import org.elasticsearch.compute.data.LongArrayBlock; -import org.elasticsearch.compute.data.LongArrayVector; import org.elasticsearch.compute.data.LongBlock; import org.elasticsearch.compute.data.Vector; +import org.elasticsearch.compute.operator.DriverContext; import org.elasticsearch.compute.operator.EvalOperator; import org.elasticsearch.xpack.ql.tree.Source; @@ -23,8 +20,12 @@ * This class is generated. Do not edit it. */ public final class ToLongFromIntEvaluator extends AbstractConvertFunction.AbstractEvaluator { - public ToLongFromIntEvaluator(EvalOperator.ExpressionEvaluator field, Source source) { + private final DriverContext driverContext; + + public ToLongFromIntEvaluator(EvalOperator.ExpressionEvaluator field, Source source, + DriverContext driverContext) { super(field, source); + this.driverContext = driverContext; } @Override @@ -38,29 +39,22 @@ public Block evalVector(Vector v) { int positionCount = v.getPositionCount(); if (vector.isConstant()) { try { - return new ConstantLongVector(evalValue(vector, 0), positionCount).asBlock(); + return driverContext.blockFactory().newConstantLongBlockWith(evalValue(vector, 0), positionCount); } catch (Exception e) { registerException(e); - return Block.constantNullBlock(positionCount); + return Block.constantNullBlock(positionCount, driverContext.blockFactory()); } } - BitSet nullsMask = null; - long[] values = new long[positionCount]; + LongBlock.Builder builder = LongBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); for (int p = 0; p < positionCount; p++) { try { - values[p] = evalValue(vector, p); + builder.appendLong(evalValue(vector, p)); } catch (Exception e) { registerException(e); - if (nullsMask == null) { - nullsMask = new BitSet(positionCount); - } - nullsMask.set(p); + builder.appendNull(); } } - return nullsMask == null - ? new LongArrayVector(values, positionCount).asBlock() - // UNORDERED, since whatever ordering there is, it isn't necessarily preserved - : new LongArrayBlock(values, positionCount, null, nullsMask, Block.MvOrdering.UNORDERED); + return builder.build(); } private static long evalValue(IntVector container, int index) { @@ -72,7 +66,7 @@ private static long evalValue(IntVector container, int index) { public Block evalBlock(Block b) { IntBlock block = (IntBlock) b; int positionCount = block.getPositionCount(); - LongBlock.Builder builder = LongBlock.newBlockBuilder(positionCount); + LongBlock.Builder builder = LongBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); for (int p = 0; p < positionCount; p++) { int valueCount = block.getValueCount(p); int start = block.getFirstValueIndex(p); diff --git a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToLongFromStringEvaluator.java b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToLongFromStringEvaluator.java index cc825664cc331..8af9a14fd81be 100644 --- a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToLongFromStringEvaluator.java +++ b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToLongFromStringEvaluator.java @@ -6,16 +6,13 @@ import java.lang.Override; import java.lang.String; -import java.util.BitSet; import org.apache.lucene.util.BytesRef; import org.elasticsearch.compute.data.Block; import org.elasticsearch.compute.data.BytesRefBlock; import org.elasticsearch.compute.data.BytesRefVector; -import org.elasticsearch.compute.data.ConstantLongVector; -import org.elasticsearch.compute.data.LongArrayBlock; -import org.elasticsearch.compute.data.LongArrayVector; import org.elasticsearch.compute.data.LongBlock; import org.elasticsearch.compute.data.Vector; +import org.elasticsearch.compute.operator.DriverContext; import org.elasticsearch.compute.operator.EvalOperator; import org.elasticsearch.xpack.ql.tree.Source; @@ -24,8 +21,12 @@ * This class is generated. Do not edit it. */ public final class ToLongFromStringEvaluator extends AbstractConvertFunction.AbstractEvaluator { - public ToLongFromStringEvaluator(EvalOperator.ExpressionEvaluator field, Source source) { + private final DriverContext driverContext; + + public ToLongFromStringEvaluator(EvalOperator.ExpressionEvaluator field, Source source, + DriverContext driverContext) { super(field, source); + this.driverContext = driverContext; } @Override @@ -40,29 +41,22 @@ public Block evalVector(Vector v) { BytesRef scratchPad = new BytesRef(); if (vector.isConstant()) { try { - return new ConstantLongVector(evalValue(vector, 0, scratchPad), positionCount).asBlock(); + return driverContext.blockFactory().newConstantLongBlockWith(evalValue(vector, 0, scratchPad), positionCount); } catch (Exception e) { registerException(e); - return Block.constantNullBlock(positionCount); + return Block.constantNullBlock(positionCount, driverContext.blockFactory()); } } - BitSet nullsMask = null; - long[] values = new long[positionCount]; + LongBlock.Builder builder = LongBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); for (int p = 0; p < positionCount; p++) { try { - values[p] = evalValue(vector, p, scratchPad); + builder.appendLong(evalValue(vector, p, scratchPad)); } catch (Exception e) { registerException(e); - if (nullsMask == null) { - nullsMask = new BitSet(positionCount); - } - nullsMask.set(p); + builder.appendNull(); } } - return nullsMask == null - ? new LongArrayVector(values, positionCount).asBlock() - // UNORDERED, since whatever ordering there is, it isn't necessarily preserved - : new LongArrayBlock(values, positionCount, null, nullsMask, Block.MvOrdering.UNORDERED); + return builder.build(); } private static long evalValue(BytesRefVector container, int index, BytesRef scratchPad) { @@ -74,7 +68,7 @@ private static long evalValue(BytesRefVector container, int index, BytesRef scra public Block evalBlock(Block b) { BytesRefBlock block = (BytesRefBlock) b; int positionCount = block.getPositionCount(); - LongBlock.Builder builder = LongBlock.newBlockBuilder(positionCount); + LongBlock.Builder builder = LongBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); BytesRef scratchPad = new BytesRef(); for (int p = 0; p < positionCount; p++) { int valueCount = block.getValueCount(p); diff --git a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToLongFromUnsignedLongEvaluator.java b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToLongFromUnsignedLongEvaluator.java index 02bef2f9f9c2d..569df205855d3 100644 --- a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToLongFromUnsignedLongEvaluator.java +++ b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToLongFromUnsignedLongEvaluator.java @@ -6,14 +6,11 @@ import java.lang.Override; import java.lang.String; -import java.util.BitSet; import org.elasticsearch.compute.data.Block; -import org.elasticsearch.compute.data.ConstantLongVector; -import org.elasticsearch.compute.data.LongArrayBlock; -import org.elasticsearch.compute.data.LongArrayVector; import org.elasticsearch.compute.data.LongBlock; import org.elasticsearch.compute.data.LongVector; import org.elasticsearch.compute.data.Vector; +import org.elasticsearch.compute.operator.DriverContext; import org.elasticsearch.compute.operator.EvalOperator; import org.elasticsearch.xpack.ql.tree.Source; @@ -22,8 +19,12 @@ * This class is generated. Do not edit it. */ public final class ToLongFromUnsignedLongEvaluator extends AbstractConvertFunction.AbstractEvaluator { - public ToLongFromUnsignedLongEvaluator(EvalOperator.ExpressionEvaluator field, Source source) { + private final DriverContext driverContext; + + public ToLongFromUnsignedLongEvaluator(EvalOperator.ExpressionEvaluator field, Source source, + DriverContext driverContext) { super(field, source); + this.driverContext = driverContext; } @Override @@ -37,29 +38,22 @@ public Block evalVector(Vector v) { int positionCount = v.getPositionCount(); if (vector.isConstant()) { try { - return new ConstantLongVector(evalValue(vector, 0), positionCount).asBlock(); + return driverContext.blockFactory().newConstantLongBlockWith(evalValue(vector, 0), positionCount); } catch (Exception e) { registerException(e); - return Block.constantNullBlock(positionCount); + return Block.constantNullBlock(positionCount, driverContext.blockFactory()); } } - BitSet nullsMask = null; - long[] values = new long[positionCount]; + LongBlock.Builder builder = LongBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); for (int p = 0; p < positionCount; p++) { try { - values[p] = evalValue(vector, p); + builder.appendLong(evalValue(vector, p)); } catch (Exception e) { registerException(e); - if (nullsMask == null) { - nullsMask = new BitSet(positionCount); - } - nullsMask.set(p); + builder.appendNull(); } } - return nullsMask == null - ? new LongArrayVector(values, positionCount).asBlock() - // UNORDERED, since whatever ordering there is, it isn't necessarily preserved - : new LongArrayBlock(values, positionCount, null, nullsMask, Block.MvOrdering.UNORDERED); + return builder.build(); } private static long evalValue(LongVector container, int index) { @@ -71,7 +65,7 @@ private static long evalValue(LongVector container, int index) { public Block evalBlock(Block b) { LongBlock block = (LongBlock) b; int positionCount = block.getPositionCount(); - LongBlock.Builder builder = LongBlock.newBlockBuilder(positionCount); + LongBlock.Builder builder = LongBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); for (int p = 0; p < positionCount; p++) { int valueCount = block.getValueCount(p); int start = block.getFirstValueIndex(p); diff --git a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToRadiansEvaluator.java b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToRadiansEvaluator.java index 33ae94093dd85..6aa373e69b7cd 100644 --- a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToRadiansEvaluator.java +++ b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToRadiansEvaluator.java @@ -6,14 +6,11 @@ import java.lang.Override; import java.lang.String; -import java.util.BitSet; import org.elasticsearch.compute.data.Block; -import org.elasticsearch.compute.data.ConstantDoubleVector; -import org.elasticsearch.compute.data.DoubleArrayBlock; -import org.elasticsearch.compute.data.DoubleArrayVector; import org.elasticsearch.compute.data.DoubleBlock; import org.elasticsearch.compute.data.DoubleVector; import org.elasticsearch.compute.data.Vector; +import org.elasticsearch.compute.operator.DriverContext; import org.elasticsearch.compute.operator.EvalOperator; import org.elasticsearch.xpack.ql.tree.Source; @@ -22,8 +19,12 @@ * This class is generated. Do not edit it. */ public final class ToRadiansEvaluator extends AbstractConvertFunction.AbstractEvaluator { - public ToRadiansEvaluator(EvalOperator.ExpressionEvaluator field, Source source) { + private final DriverContext driverContext; + + public ToRadiansEvaluator(EvalOperator.ExpressionEvaluator field, Source source, + DriverContext driverContext) { super(field, source); + this.driverContext = driverContext; } @Override @@ -37,29 +38,22 @@ public Block evalVector(Vector v) { int positionCount = v.getPositionCount(); if (vector.isConstant()) { try { - return new ConstantDoubleVector(evalValue(vector, 0), positionCount).asBlock(); + return driverContext.blockFactory().newConstantDoubleBlockWith(evalValue(vector, 0), positionCount); } catch (Exception e) { registerException(e); - return Block.constantNullBlock(positionCount); + return Block.constantNullBlock(positionCount, driverContext.blockFactory()); } } - BitSet nullsMask = null; - double[] values = new double[positionCount]; + DoubleBlock.Builder builder = DoubleBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); for (int p = 0; p < positionCount; p++) { try { - values[p] = evalValue(vector, p); + builder.appendDouble(evalValue(vector, p)); } catch (Exception e) { registerException(e); - if (nullsMask == null) { - nullsMask = new BitSet(positionCount); - } - nullsMask.set(p); + builder.appendNull(); } } - return nullsMask == null - ? new DoubleArrayVector(values, positionCount).asBlock() - // UNORDERED, since whatever ordering there is, it isn't necessarily preserved - : new DoubleArrayBlock(values, positionCount, null, nullsMask, Block.MvOrdering.UNORDERED); + return builder.build(); } private static double evalValue(DoubleVector container, int index) { @@ -71,7 +65,7 @@ private static double evalValue(DoubleVector container, int index) { public Block evalBlock(Block b) { DoubleBlock block = (DoubleBlock) b; int positionCount = block.getPositionCount(); - DoubleBlock.Builder builder = DoubleBlock.newBlockBuilder(positionCount); + DoubleBlock.Builder builder = DoubleBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); for (int p = 0; p < positionCount; p++) { int valueCount = block.getValueCount(p); int start = block.getFirstValueIndex(p); diff --git a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToStringFromBooleanEvaluator.java b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToStringFromBooleanEvaluator.java index 876344b1c35bc..8507395c6153a 100644 --- a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToStringFromBooleanEvaluator.java +++ b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToStringFromBooleanEvaluator.java @@ -6,18 +6,13 @@ import java.lang.Override; import java.lang.String; -import java.util.BitSet; import org.apache.lucene.util.BytesRef; -import org.elasticsearch.common.util.BigArrays; -import org.elasticsearch.common.util.BytesRefArray; import org.elasticsearch.compute.data.Block; import org.elasticsearch.compute.data.BooleanBlock; import org.elasticsearch.compute.data.BooleanVector; -import org.elasticsearch.compute.data.BytesRefArrayBlock; -import org.elasticsearch.compute.data.BytesRefArrayVector; import org.elasticsearch.compute.data.BytesRefBlock; -import org.elasticsearch.compute.data.ConstantBytesRefVector; import org.elasticsearch.compute.data.Vector; +import org.elasticsearch.compute.operator.DriverContext; import org.elasticsearch.compute.operator.EvalOperator; import org.elasticsearch.xpack.ql.tree.Source; @@ -26,8 +21,12 @@ * This class is generated. Do not edit it. */ public final class ToStringFromBooleanEvaluator extends AbstractConvertFunction.AbstractEvaluator { - public ToStringFromBooleanEvaluator(EvalOperator.ExpressionEvaluator field, Source source) { + private final DriverContext driverContext; + + public ToStringFromBooleanEvaluator(EvalOperator.ExpressionEvaluator field, Source source, + DriverContext driverContext) { super(field, source); + this.driverContext = driverContext; } @Override @@ -41,30 +40,22 @@ public Block evalVector(Vector v) { int positionCount = v.getPositionCount(); if (vector.isConstant()) { try { - return new ConstantBytesRefVector(evalValue(vector, 0), positionCount).asBlock(); + return driverContext.blockFactory().newConstantBytesRefBlockWith(evalValue(vector, 0), positionCount); } catch (Exception e) { registerException(e); - return Block.constantNullBlock(positionCount); + return Block.constantNullBlock(positionCount, driverContext.blockFactory()); } } - BitSet nullsMask = null; - BytesRefArray values = new BytesRefArray(positionCount, BigArrays.NON_RECYCLING_INSTANCE); + BytesRefBlock.Builder builder = BytesRefBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); for (int p = 0; p < positionCount; p++) { try { - values.append(evalValue(vector, p)); + builder.appendBytesRef(evalValue(vector, p)); } catch (Exception e) { registerException(e); - if (nullsMask == null) { - nullsMask = new BitSet(positionCount); - } - nullsMask.set(p); - values.append(BytesRefBlock.NULL_VALUE); + builder.appendNull(); } } - return nullsMask == null - ? new BytesRefArrayVector(values, positionCount).asBlock() - // UNORDERED, since whatever ordering there is, it isn't necessarily preserved - : new BytesRefArrayBlock(values, positionCount, null, nullsMask, Block.MvOrdering.UNORDERED); + return builder.build(); } private static BytesRef evalValue(BooleanVector container, int index) { @@ -76,7 +67,7 @@ private static BytesRef evalValue(BooleanVector container, int index) { public Block evalBlock(Block b) { BooleanBlock block = (BooleanBlock) b; int positionCount = block.getPositionCount(); - BytesRefBlock.Builder builder = BytesRefBlock.newBlockBuilder(positionCount); + BytesRefBlock.Builder builder = BytesRefBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); for (int p = 0; p < positionCount; p++) { int valueCount = block.getValueCount(p); int start = block.getFirstValueIndex(p); diff --git a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToStringFromDatetimeEvaluator.java b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToStringFromDatetimeEvaluator.java index 8aa5148b21de4..7d6bf029fe80b 100644 --- a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToStringFromDatetimeEvaluator.java +++ b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToStringFromDatetimeEvaluator.java @@ -6,18 +6,13 @@ import java.lang.Override; import java.lang.String; -import java.util.BitSet; import org.apache.lucene.util.BytesRef; -import org.elasticsearch.common.util.BigArrays; -import org.elasticsearch.common.util.BytesRefArray; import org.elasticsearch.compute.data.Block; -import org.elasticsearch.compute.data.BytesRefArrayBlock; -import org.elasticsearch.compute.data.BytesRefArrayVector; import org.elasticsearch.compute.data.BytesRefBlock; -import org.elasticsearch.compute.data.ConstantBytesRefVector; import org.elasticsearch.compute.data.LongBlock; import org.elasticsearch.compute.data.LongVector; import org.elasticsearch.compute.data.Vector; +import org.elasticsearch.compute.operator.DriverContext; import org.elasticsearch.compute.operator.EvalOperator; import org.elasticsearch.xpack.ql.tree.Source; @@ -26,8 +21,12 @@ * This class is generated. Do not edit it. */ public final class ToStringFromDatetimeEvaluator extends AbstractConvertFunction.AbstractEvaluator { - public ToStringFromDatetimeEvaluator(EvalOperator.ExpressionEvaluator field, Source source) { + private final DriverContext driverContext; + + public ToStringFromDatetimeEvaluator(EvalOperator.ExpressionEvaluator field, Source source, + DriverContext driverContext) { super(field, source); + this.driverContext = driverContext; } @Override @@ -41,30 +40,22 @@ public Block evalVector(Vector v) { int positionCount = v.getPositionCount(); if (vector.isConstant()) { try { - return new ConstantBytesRefVector(evalValue(vector, 0), positionCount).asBlock(); + return driverContext.blockFactory().newConstantBytesRefBlockWith(evalValue(vector, 0), positionCount); } catch (Exception e) { registerException(e); - return Block.constantNullBlock(positionCount); + return Block.constantNullBlock(positionCount, driverContext.blockFactory()); } } - BitSet nullsMask = null; - BytesRefArray values = new BytesRefArray(positionCount, BigArrays.NON_RECYCLING_INSTANCE); + BytesRefBlock.Builder builder = BytesRefBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); for (int p = 0; p < positionCount; p++) { try { - values.append(evalValue(vector, p)); + builder.appendBytesRef(evalValue(vector, p)); } catch (Exception e) { registerException(e); - if (nullsMask == null) { - nullsMask = new BitSet(positionCount); - } - nullsMask.set(p); - values.append(BytesRefBlock.NULL_VALUE); + builder.appendNull(); } } - return nullsMask == null - ? new BytesRefArrayVector(values, positionCount).asBlock() - // UNORDERED, since whatever ordering there is, it isn't necessarily preserved - : new BytesRefArrayBlock(values, positionCount, null, nullsMask, Block.MvOrdering.UNORDERED); + return builder.build(); } private static BytesRef evalValue(LongVector container, int index) { @@ -76,7 +67,7 @@ private static BytesRef evalValue(LongVector container, int index) { public Block evalBlock(Block b) { LongBlock block = (LongBlock) b; int positionCount = block.getPositionCount(); - BytesRefBlock.Builder builder = BytesRefBlock.newBlockBuilder(positionCount); + BytesRefBlock.Builder builder = BytesRefBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); for (int p = 0; p < positionCount; p++) { int valueCount = block.getValueCount(p); int start = block.getFirstValueIndex(p); diff --git a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToStringFromDoubleEvaluator.java b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToStringFromDoubleEvaluator.java index 8c7994a3c0a68..e0aa134286723 100644 --- a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToStringFromDoubleEvaluator.java +++ b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToStringFromDoubleEvaluator.java @@ -6,18 +6,13 @@ import java.lang.Override; import java.lang.String; -import java.util.BitSet; import org.apache.lucene.util.BytesRef; -import org.elasticsearch.common.util.BigArrays; -import org.elasticsearch.common.util.BytesRefArray; import org.elasticsearch.compute.data.Block; -import org.elasticsearch.compute.data.BytesRefArrayBlock; -import org.elasticsearch.compute.data.BytesRefArrayVector; import org.elasticsearch.compute.data.BytesRefBlock; -import org.elasticsearch.compute.data.ConstantBytesRefVector; import org.elasticsearch.compute.data.DoubleBlock; import org.elasticsearch.compute.data.DoubleVector; import org.elasticsearch.compute.data.Vector; +import org.elasticsearch.compute.operator.DriverContext; import org.elasticsearch.compute.operator.EvalOperator; import org.elasticsearch.xpack.ql.tree.Source; @@ -26,8 +21,12 @@ * This class is generated. Do not edit it. */ public final class ToStringFromDoubleEvaluator extends AbstractConvertFunction.AbstractEvaluator { - public ToStringFromDoubleEvaluator(EvalOperator.ExpressionEvaluator field, Source source) { + private final DriverContext driverContext; + + public ToStringFromDoubleEvaluator(EvalOperator.ExpressionEvaluator field, Source source, + DriverContext driverContext) { super(field, source); + this.driverContext = driverContext; } @Override @@ -41,30 +40,22 @@ public Block evalVector(Vector v) { int positionCount = v.getPositionCount(); if (vector.isConstant()) { try { - return new ConstantBytesRefVector(evalValue(vector, 0), positionCount).asBlock(); + return driverContext.blockFactory().newConstantBytesRefBlockWith(evalValue(vector, 0), positionCount); } catch (Exception e) { registerException(e); - return Block.constantNullBlock(positionCount); + return Block.constantNullBlock(positionCount, driverContext.blockFactory()); } } - BitSet nullsMask = null; - BytesRefArray values = new BytesRefArray(positionCount, BigArrays.NON_RECYCLING_INSTANCE); + BytesRefBlock.Builder builder = BytesRefBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); for (int p = 0; p < positionCount; p++) { try { - values.append(evalValue(vector, p)); + builder.appendBytesRef(evalValue(vector, p)); } catch (Exception e) { registerException(e); - if (nullsMask == null) { - nullsMask = new BitSet(positionCount); - } - nullsMask.set(p); - values.append(BytesRefBlock.NULL_VALUE); + builder.appendNull(); } } - return nullsMask == null - ? new BytesRefArrayVector(values, positionCount).asBlock() - // UNORDERED, since whatever ordering there is, it isn't necessarily preserved - : new BytesRefArrayBlock(values, positionCount, null, nullsMask, Block.MvOrdering.UNORDERED); + return builder.build(); } private static BytesRef evalValue(DoubleVector container, int index) { @@ -76,7 +67,7 @@ private static BytesRef evalValue(DoubleVector container, int index) { public Block evalBlock(Block b) { DoubleBlock block = (DoubleBlock) b; int positionCount = block.getPositionCount(); - BytesRefBlock.Builder builder = BytesRefBlock.newBlockBuilder(positionCount); + BytesRefBlock.Builder builder = BytesRefBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); for (int p = 0; p < positionCount; p++) { int valueCount = block.getValueCount(p); int start = block.getFirstValueIndex(p); diff --git a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToStringFromIPEvaluator.java b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToStringFromIPEvaluator.java index 4e0249939cc91..7ef6c3df27025 100644 --- a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToStringFromIPEvaluator.java +++ b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToStringFromIPEvaluator.java @@ -6,17 +6,12 @@ import java.lang.Override; import java.lang.String; -import java.util.BitSet; import org.apache.lucene.util.BytesRef; -import org.elasticsearch.common.util.BigArrays; -import org.elasticsearch.common.util.BytesRefArray; import org.elasticsearch.compute.data.Block; -import org.elasticsearch.compute.data.BytesRefArrayBlock; -import org.elasticsearch.compute.data.BytesRefArrayVector; import org.elasticsearch.compute.data.BytesRefBlock; import org.elasticsearch.compute.data.BytesRefVector; -import org.elasticsearch.compute.data.ConstantBytesRefVector; import org.elasticsearch.compute.data.Vector; +import org.elasticsearch.compute.operator.DriverContext; import org.elasticsearch.compute.operator.EvalOperator; import org.elasticsearch.xpack.ql.tree.Source; @@ -25,8 +20,12 @@ * This class is generated. Do not edit it. */ public final class ToStringFromIPEvaluator extends AbstractConvertFunction.AbstractEvaluator { - public ToStringFromIPEvaluator(EvalOperator.ExpressionEvaluator field, Source source) { + private final DriverContext driverContext; + + public ToStringFromIPEvaluator(EvalOperator.ExpressionEvaluator field, Source source, + DriverContext driverContext) { super(field, source); + this.driverContext = driverContext; } @Override @@ -41,30 +40,22 @@ public Block evalVector(Vector v) { BytesRef scratchPad = new BytesRef(); if (vector.isConstant()) { try { - return new ConstantBytesRefVector(evalValue(vector, 0, scratchPad), positionCount).asBlock(); + return driverContext.blockFactory().newConstantBytesRefBlockWith(evalValue(vector, 0, scratchPad), positionCount); } catch (Exception e) { registerException(e); - return Block.constantNullBlock(positionCount); + return Block.constantNullBlock(positionCount, driverContext.blockFactory()); } } - BitSet nullsMask = null; - BytesRefArray values = new BytesRefArray(positionCount, BigArrays.NON_RECYCLING_INSTANCE); + BytesRefBlock.Builder builder = BytesRefBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); for (int p = 0; p < positionCount; p++) { try { - values.append(evalValue(vector, p, scratchPad)); + builder.appendBytesRef(evalValue(vector, p, scratchPad)); } catch (Exception e) { registerException(e); - if (nullsMask == null) { - nullsMask = new BitSet(positionCount); - } - nullsMask.set(p); - values.append(BytesRefBlock.NULL_VALUE); + builder.appendNull(); } } - return nullsMask == null - ? new BytesRefArrayVector(values, positionCount).asBlock() - // UNORDERED, since whatever ordering there is, it isn't necessarily preserved - : new BytesRefArrayBlock(values, positionCount, null, nullsMask, Block.MvOrdering.UNORDERED); + return builder.build(); } private static BytesRef evalValue(BytesRefVector container, int index, BytesRef scratchPad) { @@ -76,7 +67,7 @@ private static BytesRef evalValue(BytesRefVector container, int index, BytesRef public Block evalBlock(Block b) { BytesRefBlock block = (BytesRefBlock) b; int positionCount = block.getPositionCount(); - BytesRefBlock.Builder builder = BytesRefBlock.newBlockBuilder(positionCount); + BytesRefBlock.Builder builder = BytesRefBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); BytesRef scratchPad = new BytesRef(); for (int p = 0; p < positionCount; p++) { int valueCount = block.getValueCount(p); diff --git a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToStringFromIntEvaluator.java b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToStringFromIntEvaluator.java index d076b38f49b91..abe206d5a5152 100644 --- a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToStringFromIntEvaluator.java +++ b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToStringFromIntEvaluator.java @@ -6,18 +6,13 @@ import java.lang.Override; import java.lang.String; -import java.util.BitSet; import org.apache.lucene.util.BytesRef; -import org.elasticsearch.common.util.BigArrays; -import org.elasticsearch.common.util.BytesRefArray; import org.elasticsearch.compute.data.Block; -import org.elasticsearch.compute.data.BytesRefArrayBlock; -import org.elasticsearch.compute.data.BytesRefArrayVector; import org.elasticsearch.compute.data.BytesRefBlock; -import org.elasticsearch.compute.data.ConstantBytesRefVector; import org.elasticsearch.compute.data.IntBlock; import org.elasticsearch.compute.data.IntVector; import org.elasticsearch.compute.data.Vector; +import org.elasticsearch.compute.operator.DriverContext; import org.elasticsearch.compute.operator.EvalOperator; import org.elasticsearch.xpack.ql.tree.Source; @@ -26,8 +21,12 @@ * This class is generated. Do not edit it. */ public final class ToStringFromIntEvaluator extends AbstractConvertFunction.AbstractEvaluator { - public ToStringFromIntEvaluator(EvalOperator.ExpressionEvaluator field, Source source) { + private final DriverContext driverContext; + + public ToStringFromIntEvaluator(EvalOperator.ExpressionEvaluator field, Source source, + DriverContext driverContext) { super(field, source); + this.driverContext = driverContext; } @Override @@ -41,30 +40,22 @@ public Block evalVector(Vector v) { int positionCount = v.getPositionCount(); if (vector.isConstant()) { try { - return new ConstantBytesRefVector(evalValue(vector, 0), positionCount).asBlock(); + return driverContext.blockFactory().newConstantBytesRefBlockWith(evalValue(vector, 0), positionCount); } catch (Exception e) { registerException(e); - return Block.constantNullBlock(positionCount); + return Block.constantNullBlock(positionCount, driverContext.blockFactory()); } } - BitSet nullsMask = null; - BytesRefArray values = new BytesRefArray(positionCount, BigArrays.NON_RECYCLING_INSTANCE); + BytesRefBlock.Builder builder = BytesRefBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); for (int p = 0; p < positionCount; p++) { try { - values.append(evalValue(vector, p)); + builder.appendBytesRef(evalValue(vector, p)); } catch (Exception e) { registerException(e); - if (nullsMask == null) { - nullsMask = new BitSet(positionCount); - } - nullsMask.set(p); - values.append(BytesRefBlock.NULL_VALUE); + builder.appendNull(); } } - return nullsMask == null - ? new BytesRefArrayVector(values, positionCount).asBlock() - // UNORDERED, since whatever ordering there is, it isn't necessarily preserved - : new BytesRefArrayBlock(values, positionCount, null, nullsMask, Block.MvOrdering.UNORDERED); + return builder.build(); } private static BytesRef evalValue(IntVector container, int index) { @@ -76,7 +67,7 @@ private static BytesRef evalValue(IntVector container, int index) { public Block evalBlock(Block b) { IntBlock block = (IntBlock) b; int positionCount = block.getPositionCount(); - BytesRefBlock.Builder builder = BytesRefBlock.newBlockBuilder(positionCount); + BytesRefBlock.Builder builder = BytesRefBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); for (int p = 0; p < positionCount; p++) { int valueCount = block.getValueCount(p); int start = block.getFirstValueIndex(p); diff --git a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToStringFromLongEvaluator.java b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToStringFromLongEvaluator.java index 90448cb992cd2..be6c2648f9eb4 100644 --- a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToStringFromLongEvaluator.java +++ b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToStringFromLongEvaluator.java @@ -6,18 +6,13 @@ import java.lang.Override; import java.lang.String; -import java.util.BitSet; import org.apache.lucene.util.BytesRef; -import org.elasticsearch.common.util.BigArrays; -import org.elasticsearch.common.util.BytesRefArray; import org.elasticsearch.compute.data.Block; -import org.elasticsearch.compute.data.BytesRefArrayBlock; -import org.elasticsearch.compute.data.BytesRefArrayVector; import org.elasticsearch.compute.data.BytesRefBlock; -import org.elasticsearch.compute.data.ConstantBytesRefVector; import org.elasticsearch.compute.data.LongBlock; import org.elasticsearch.compute.data.LongVector; import org.elasticsearch.compute.data.Vector; +import org.elasticsearch.compute.operator.DriverContext; import org.elasticsearch.compute.operator.EvalOperator; import org.elasticsearch.xpack.ql.tree.Source; @@ -26,8 +21,12 @@ * This class is generated. Do not edit it. */ public final class ToStringFromLongEvaluator extends AbstractConvertFunction.AbstractEvaluator { - public ToStringFromLongEvaluator(EvalOperator.ExpressionEvaluator field, Source source) { + private final DriverContext driverContext; + + public ToStringFromLongEvaluator(EvalOperator.ExpressionEvaluator field, Source source, + DriverContext driverContext) { super(field, source); + this.driverContext = driverContext; } @Override @@ -41,30 +40,22 @@ public Block evalVector(Vector v) { int positionCount = v.getPositionCount(); if (vector.isConstant()) { try { - return new ConstantBytesRefVector(evalValue(vector, 0), positionCount).asBlock(); + return driverContext.blockFactory().newConstantBytesRefBlockWith(evalValue(vector, 0), positionCount); } catch (Exception e) { registerException(e); - return Block.constantNullBlock(positionCount); + return Block.constantNullBlock(positionCount, driverContext.blockFactory()); } } - BitSet nullsMask = null; - BytesRefArray values = new BytesRefArray(positionCount, BigArrays.NON_RECYCLING_INSTANCE); + BytesRefBlock.Builder builder = BytesRefBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); for (int p = 0; p < positionCount; p++) { try { - values.append(evalValue(vector, p)); + builder.appendBytesRef(evalValue(vector, p)); } catch (Exception e) { registerException(e); - if (nullsMask == null) { - nullsMask = new BitSet(positionCount); - } - nullsMask.set(p); - values.append(BytesRefBlock.NULL_VALUE); + builder.appendNull(); } } - return nullsMask == null - ? new BytesRefArrayVector(values, positionCount).asBlock() - // UNORDERED, since whatever ordering there is, it isn't necessarily preserved - : new BytesRefArrayBlock(values, positionCount, null, nullsMask, Block.MvOrdering.UNORDERED); + return builder.build(); } private static BytesRef evalValue(LongVector container, int index) { @@ -76,7 +67,7 @@ private static BytesRef evalValue(LongVector container, int index) { public Block evalBlock(Block b) { LongBlock block = (LongBlock) b; int positionCount = block.getPositionCount(); - BytesRefBlock.Builder builder = BytesRefBlock.newBlockBuilder(positionCount); + BytesRefBlock.Builder builder = BytesRefBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); for (int p = 0; p < positionCount; p++) { int valueCount = block.getValueCount(p); int start = block.getFirstValueIndex(p); diff --git a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToStringFromUnsignedLongEvaluator.java b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToStringFromUnsignedLongEvaluator.java index 91e31c9626b5e..9ba24301875d2 100644 --- a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToStringFromUnsignedLongEvaluator.java +++ b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToStringFromUnsignedLongEvaluator.java @@ -6,18 +6,13 @@ import java.lang.Override; import java.lang.String; -import java.util.BitSet; import org.apache.lucene.util.BytesRef; -import org.elasticsearch.common.util.BigArrays; -import org.elasticsearch.common.util.BytesRefArray; import org.elasticsearch.compute.data.Block; -import org.elasticsearch.compute.data.BytesRefArrayBlock; -import org.elasticsearch.compute.data.BytesRefArrayVector; import org.elasticsearch.compute.data.BytesRefBlock; -import org.elasticsearch.compute.data.ConstantBytesRefVector; import org.elasticsearch.compute.data.LongBlock; import org.elasticsearch.compute.data.LongVector; import org.elasticsearch.compute.data.Vector; +import org.elasticsearch.compute.operator.DriverContext; import org.elasticsearch.compute.operator.EvalOperator; import org.elasticsearch.xpack.ql.tree.Source; @@ -26,8 +21,12 @@ * This class is generated. Do not edit it. */ public final class ToStringFromUnsignedLongEvaluator extends AbstractConvertFunction.AbstractEvaluator { - public ToStringFromUnsignedLongEvaluator(EvalOperator.ExpressionEvaluator field, Source source) { + private final DriverContext driverContext; + + public ToStringFromUnsignedLongEvaluator(EvalOperator.ExpressionEvaluator field, Source source, + DriverContext driverContext) { super(field, source); + this.driverContext = driverContext; } @Override @@ -41,30 +40,22 @@ public Block evalVector(Vector v) { int positionCount = v.getPositionCount(); if (vector.isConstant()) { try { - return new ConstantBytesRefVector(evalValue(vector, 0), positionCount).asBlock(); + return driverContext.blockFactory().newConstantBytesRefBlockWith(evalValue(vector, 0), positionCount); } catch (Exception e) { registerException(e); - return Block.constantNullBlock(positionCount); + return Block.constantNullBlock(positionCount, driverContext.blockFactory()); } } - BitSet nullsMask = null; - BytesRefArray values = new BytesRefArray(positionCount, BigArrays.NON_RECYCLING_INSTANCE); + BytesRefBlock.Builder builder = BytesRefBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); for (int p = 0; p < positionCount; p++) { try { - values.append(evalValue(vector, p)); + builder.appendBytesRef(evalValue(vector, p)); } catch (Exception e) { registerException(e); - if (nullsMask == null) { - nullsMask = new BitSet(positionCount); - } - nullsMask.set(p); - values.append(BytesRefBlock.NULL_VALUE); + builder.appendNull(); } } - return nullsMask == null - ? new BytesRefArrayVector(values, positionCount).asBlock() - // UNORDERED, since whatever ordering there is, it isn't necessarily preserved - : new BytesRefArrayBlock(values, positionCount, null, nullsMask, Block.MvOrdering.UNORDERED); + return builder.build(); } private static BytesRef evalValue(LongVector container, int index) { @@ -76,7 +67,7 @@ private static BytesRef evalValue(LongVector container, int index) { public Block evalBlock(Block b) { LongBlock block = (LongBlock) b; int positionCount = block.getPositionCount(); - BytesRefBlock.Builder builder = BytesRefBlock.newBlockBuilder(positionCount); + BytesRefBlock.Builder builder = BytesRefBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); for (int p = 0; p < positionCount; p++) { int valueCount = block.getValueCount(p); int start = block.getFirstValueIndex(p); diff --git a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToStringFromVersionEvaluator.java b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToStringFromVersionEvaluator.java index 281b881bd6141..69d2e0e106fa0 100644 --- a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToStringFromVersionEvaluator.java +++ b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToStringFromVersionEvaluator.java @@ -6,17 +6,12 @@ import java.lang.Override; import java.lang.String; -import java.util.BitSet; import org.apache.lucene.util.BytesRef; -import org.elasticsearch.common.util.BigArrays; -import org.elasticsearch.common.util.BytesRefArray; import org.elasticsearch.compute.data.Block; -import org.elasticsearch.compute.data.BytesRefArrayBlock; -import org.elasticsearch.compute.data.BytesRefArrayVector; import org.elasticsearch.compute.data.BytesRefBlock; import org.elasticsearch.compute.data.BytesRefVector; -import org.elasticsearch.compute.data.ConstantBytesRefVector; import org.elasticsearch.compute.data.Vector; +import org.elasticsearch.compute.operator.DriverContext; import org.elasticsearch.compute.operator.EvalOperator; import org.elasticsearch.xpack.ql.tree.Source; @@ -25,8 +20,12 @@ * This class is generated. Do not edit it. */ public final class ToStringFromVersionEvaluator extends AbstractConvertFunction.AbstractEvaluator { - public ToStringFromVersionEvaluator(EvalOperator.ExpressionEvaluator field, Source source) { + private final DriverContext driverContext; + + public ToStringFromVersionEvaluator(EvalOperator.ExpressionEvaluator field, Source source, + DriverContext driverContext) { super(field, source); + this.driverContext = driverContext; } @Override @@ -41,30 +40,22 @@ public Block evalVector(Vector v) { BytesRef scratchPad = new BytesRef(); if (vector.isConstant()) { try { - return new ConstantBytesRefVector(evalValue(vector, 0, scratchPad), positionCount).asBlock(); + return driverContext.blockFactory().newConstantBytesRefBlockWith(evalValue(vector, 0, scratchPad), positionCount); } catch (Exception e) { registerException(e); - return Block.constantNullBlock(positionCount); + return Block.constantNullBlock(positionCount, driverContext.blockFactory()); } } - BitSet nullsMask = null; - BytesRefArray values = new BytesRefArray(positionCount, BigArrays.NON_RECYCLING_INSTANCE); + BytesRefBlock.Builder builder = BytesRefBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); for (int p = 0; p < positionCount; p++) { try { - values.append(evalValue(vector, p, scratchPad)); + builder.appendBytesRef(evalValue(vector, p, scratchPad)); } catch (Exception e) { registerException(e); - if (nullsMask == null) { - nullsMask = new BitSet(positionCount); - } - nullsMask.set(p); - values.append(BytesRefBlock.NULL_VALUE); + builder.appendNull(); } } - return nullsMask == null - ? new BytesRefArrayVector(values, positionCount).asBlock() - // UNORDERED, since whatever ordering there is, it isn't necessarily preserved - : new BytesRefArrayBlock(values, positionCount, null, nullsMask, Block.MvOrdering.UNORDERED); + return builder.build(); } private static BytesRef evalValue(BytesRefVector container, int index, BytesRef scratchPad) { @@ -76,7 +67,7 @@ private static BytesRef evalValue(BytesRefVector container, int index, BytesRef public Block evalBlock(Block b) { BytesRefBlock block = (BytesRefBlock) b; int positionCount = block.getPositionCount(); - BytesRefBlock.Builder builder = BytesRefBlock.newBlockBuilder(positionCount); + BytesRefBlock.Builder builder = BytesRefBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); BytesRef scratchPad = new BytesRef(); for (int p = 0; p < positionCount; p++) { int valueCount = block.getValueCount(p); diff --git a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToUnsignedLongFromBooleanEvaluator.java b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToUnsignedLongFromBooleanEvaluator.java index ec8b16568c380..541e5b8c7af11 100644 --- a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToUnsignedLongFromBooleanEvaluator.java +++ b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToUnsignedLongFromBooleanEvaluator.java @@ -6,15 +6,12 @@ import java.lang.Override; import java.lang.String; -import java.util.BitSet; import org.elasticsearch.compute.data.Block; import org.elasticsearch.compute.data.BooleanBlock; import org.elasticsearch.compute.data.BooleanVector; -import org.elasticsearch.compute.data.ConstantLongVector; -import org.elasticsearch.compute.data.LongArrayBlock; -import org.elasticsearch.compute.data.LongArrayVector; import org.elasticsearch.compute.data.LongBlock; import org.elasticsearch.compute.data.Vector; +import org.elasticsearch.compute.operator.DriverContext; import org.elasticsearch.compute.operator.EvalOperator; import org.elasticsearch.xpack.ql.tree.Source; @@ -23,8 +20,12 @@ * This class is generated. Do not edit it. */ public final class ToUnsignedLongFromBooleanEvaluator extends AbstractConvertFunction.AbstractEvaluator { - public ToUnsignedLongFromBooleanEvaluator(EvalOperator.ExpressionEvaluator field, Source source) { + private final DriverContext driverContext; + + public ToUnsignedLongFromBooleanEvaluator(EvalOperator.ExpressionEvaluator field, Source source, + DriverContext driverContext) { super(field, source); + this.driverContext = driverContext; } @Override @@ -38,29 +39,22 @@ public Block evalVector(Vector v) { int positionCount = v.getPositionCount(); if (vector.isConstant()) { try { - return new ConstantLongVector(evalValue(vector, 0), positionCount).asBlock(); + return driverContext.blockFactory().newConstantLongBlockWith(evalValue(vector, 0), positionCount); } catch (Exception e) { registerException(e); - return Block.constantNullBlock(positionCount); + return Block.constantNullBlock(positionCount, driverContext.blockFactory()); } } - BitSet nullsMask = null; - long[] values = new long[positionCount]; + LongBlock.Builder builder = LongBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); for (int p = 0; p < positionCount; p++) { try { - values[p] = evalValue(vector, p); + builder.appendLong(evalValue(vector, p)); } catch (Exception e) { registerException(e); - if (nullsMask == null) { - nullsMask = new BitSet(positionCount); - } - nullsMask.set(p); + builder.appendNull(); } } - return nullsMask == null - ? new LongArrayVector(values, positionCount).asBlock() - // UNORDERED, since whatever ordering there is, it isn't necessarily preserved - : new LongArrayBlock(values, positionCount, null, nullsMask, Block.MvOrdering.UNORDERED); + return builder.build(); } private static long evalValue(BooleanVector container, int index) { @@ -72,7 +66,7 @@ private static long evalValue(BooleanVector container, int index) { public Block evalBlock(Block b) { BooleanBlock block = (BooleanBlock) b; int positionCount = block.getPositionCount(); - LongBlock.Builder builder = LongBlock.newBlockBuilder(positionCount); + LongBlock.Builder builder = LongBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); for (int p = 0; p < positionCount; p++) { int valueCount = block.getValueCount(p); int start = block.getFirstValueIndex(p); diff --git a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToUnsignedLongFromDoubleEvaluator.java b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToUnsignedLongFromDoubleEvaluator.java index 2ada365ce848e..89c896ccf1f43 100644 --- a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToUnsignedLongFromDoubleEvaluator.java +++ b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToUnsignedLongFromDoubleEvaluator.java @@ -6,15 +6,12 @@ import java.lang.Override; import java.lang.String; -import java.util.BitSet; import org.elasticsearch.compute.data.Block; -import org.elasticsearch.compute.data.ConstantLongVector; import org.elasticsearch.compute.data.DoubleBlock; import org.elasticsearch.compute.data.DoubleVector; -import org.elasticsearch.compute.data.LongArrayBlock; -import org.elasticsearch.compute.data.LongArrayVector; import org.elasticsearch.compute.data.LongBlock; import org.elasticsearch.compute.data.Vector; +import org.elasticsearch.compute.operator.DriverContext; import org.elasticsearch.compute.operator.EvalOperator; import org.elasticsearch.xpack.ql.tree.Source; @@ -23,8 +20,12 @@ * This class is generated. Do not edit it. */ public final class ToUnsignedLongFromDoubleEvaluator extends AbstractConvertFunction.AbstractEvaluator { - public ToUnsignedLongFromDoubleEvaluator(EvalOperator.ExpressionEvaluator field, Source source) { + private final DriverContext driverContext; + + public ToUnsignedLongFromDoubleEvaluator(EvalOperator.ExpressionEvaluator field, Source source, + DriverContext driverContext) { super(field, source); + this.driverContext = driverContext; } @Override @@ -38,29 +39,22 @@ public Block evalVector(Vector v) { int positionCount = v.getPositionCount(); if (vector.isConstant()) { try { - return new ConstantLongVector(evalValue(vector, 0), positionCount).asBlock(); + return driverContext.blockFactory().newConstantLongBlockWith(evalValue(vector, 0), positionCount); } catch (Exception e) { registerException(e); - return Block.constantNullBlock(positionCount); + return Block.constantNullBlock(positionCount, driverContext.blockFactory()); } } - BitSet nullsMask = null; - long[] values = new long[positionCount]; + LongBlock.Builder builder = LongBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); for (int p = 0; p < positionCount; p++) { try { - values[p] = evalValue(vector, p); + builder.appendLong(evalValue(vector, p)); } catch (Exception e) { registerException(e); - if (nullsMask == null) { - nullsMask = new BitSet(positionCount); - } - nullsMask.set(p); + builder.appendNull(); } } - return nullsMask == null - ? new LongArrayVector(values, positionCount).asBlock() - // UNORDERED, since whatever ordering there is, it isn't necessarily preserved - : new LongArrayBlock(values, positionCount, null, nullsMask, Block.MvOrdering.UNORDERED); + return builder.build(); } private static long evalValue(DoubleVector container, int index) { @@ -72,7 +66,7 @@ private static long evalValue(DoubleVector container, int index) { public Block evalBlock(Block b) { DoubleBlock block = (DoubleBlock) b; int positionCount = block.getPositionCount(); - LongBlock.Builder builder = LongBlock.newBlockBuilder(positionCount); + LongBlock.Builder builder = LongBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); for (int p = 0; p < positionCount; p++) { int valueCount = block.getValueCount(p); int start = block.getFirstValueIndex(p); diff --git a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToUnsignedLongFromIntEvaluator.java b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToUnsignedLongFromIntEvaluator.java index 9acad2f9481a6..3c78c24ea7b01 100644 --- a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToUnsignedLongFromIntEvaluator.java +++ b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToUnsignedLongFromIntEvaluator.java @@ -6,15 +6,12 @@ import java.lang.Override; import java.lang.String; -import java.util.BitSet; import org.elasticsearch.compute.data.Block; -import org.elasticsearch.compute.data.ConstantLongVector; import org.elasticsearch.compute.data.IntBlock; import org.elasticsearch.compute.data.IntVector; -import org.elasticsearch.compute.data.LongArrayBlock; -import org.elasticsearch.compute.data.LongArrayVector; import org.elasticsearch.compute.data.LongBlock; import org.elasticsearch.compute.data.Vector; +import org.elasticsearch.compute.operator.DriverContext; import org.elasticsearch.compute.operator.EvalOperator; import org.elasticsearch.xpack.ql.tree.Source; @@ -23,8 +20,12 @@ * This class is generated. Do not edit it. */ public final class ToUnsignedLongFromIntEvaluator extends AbstractConvertFunction.AbstractEvaluator { - public ToUnsignedLongFromIntEvaluator(EvalOperator.ExpressionEvaluator field, Source source) { + private final DriverContext driverContext; + + public ToUnsignedLongFromIntEvaluator(EvalOperator.ExpressionEvaluator field, Source source, + DriverContext driverContext) { super(field, source); + this.driverContext = driverContext; } @Override @@ -38,29 +39,22 @@ public Block evalVector(Vector v) { int positionCount = v.getPositionCount(); if (vector.isConstant()) { try { - return new ConstantLongVector(evalValue(vector, 0), positionCount).asBlock(); + return driverContext.blockFactory().newConstantLongBlockWith(evalValue(vector, 0), positionCount); } catch (Exception e) { registerException(e); - return Block.constantNullBlock(positionCount); + return Block.constantNullBlock(positionCount, driverContext.blockFactory()); } } - BitSet nullsMask = null; - long[] values = new long[positionCount]; + LongBlock.Builder builder = LongBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); for (int p = 0; p < positionCount; p++) { try { - values[p] = evalValue(vector, p); + builder.appendLong(evalValue(vector, p)); } catch (Exception e) { registerException(e); - if (nullsMask == null) { - nullsMask = new BitSet(positionCount); - } - nullsMask.set(p); + builder.appendNull(); } } - return nullsMask == null - ? new LongArrayVector(values, positionCount).asBlock() - // UNORDERED, since whatever ordering there is, it isn't necessarily preserved - : new LongArrayBlock(values, positionCount, null, nullsMask, Block.MvOrdering.UNORDERED); + return builder.build(); } private static long evalValue(IntVector container, int index) { @@ -72,7 +66,7 @@ private static long evalValue(IntVector container, int index) { public Block evalBlock(Block b) { IntBlock block = (IntBlock) b; int positionCount = block.getPositionCount(); - LongBlock.Builder builder = LongBlock.newBlockBuilder(positionCount); + LongBlock.Builder builder = LongBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); for (int p = 0; p < positionCount; p++) { int valueCount = block.getValueCount(p); int start = block.getFirstValueIndex(p); diff --git a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToUnsignedLongFromLongEvaluator.java b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToUnsignedLongFromLongEvaluator.java index 0cb7da2ed230f..0c0cb9ebfb525 100644 --- a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToUnsignedLongFromLongEvaluator.java +++ b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToUnsignedLongFromLongEvaluator.java @@ -6,14 +6,11 @@ import java.lang.Override; import java.lang.String; -import java.util.BitSet; import org.elasticsearch.compute.data.Block; -import org.elasticsearch.compute.data.ConstantLongVector; -import org.elasticsearch.compute.data.LongArrayBlock; -import org.elasticsearch.compute.data.LongArrayVector; import org.elasticsearch.compute.data.LongBlock; import org.elasticsearch.compute.data.LongVector; import org.elasticsearch.compute.data.Vector; +import org.elasticsearch.compute.operator.DriverContext; import org.elasticsearch.compute.operator.EvalOperator; import org.elasticsearch.xpack.ql.tree.Source; @@ -22,8 +19,12 @@ * This class is generated. Do not edit it. */ public final class ToUnsignedLongFromLongEvaluator extends AbstractConvertFunction.AbstractEvaluator { - public ToUnsignedLongFromLongEvaluator(EvalOperator.ExpressionEvaluator field, Source source) { + private final DriverContext driverContext; + + public ToUnsignedLongFromLongEvaluator(EvalOperator.ExpressionEvaluator field, Source source, + DriverContext driverContext) { super(field, source); + this.driverContext = driverContext; } @Override @@ -37,29 +38,22 @@ public Block evalVector(Vector v) { int positionCount = v.getPositionCount(); if (vector.isConstant()) { try { - return new ConstantLongVector(evalValue(vector, 0), positionCount).asBlock(); + return driverContext.blockFactory().newConstantLongBlockWith(evalValue(vector, 0), positionCount); } catch (Exception e) { registerException(e); - return Block.constantNullBlock(positionCount); + return Block.constantNullBlock(positionCount, driverContext.blockFactory()); } } - BitSet nullsMask = null; - long[] values = new long[positionCount]; + LongBlock.Builder builder = LongBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); for (int p = 0; p < positionCount; p++) { try { - values[p] = evalValue(vector, p); + builder.appendLong(evalValue(vector, p)); } catch (Exception e) { registerException(e); - if (nullsMask == null) { - nullsMask = new BitSet(positionCount); - } - nullsMask.set(p); + builder.appendNull(); } } - return nullsMask == null - ? new LongArrayVector(values, positionCount).asBlock() - // UNORDERED, since whatever ordering there is, it isn't necessarily preserved - : new LongArrayBlock(values, positionCount, null, nullsMask, Block.MvOrdering.UNORDERED); + return builder.build(); } private static long evalValue(LongVector container, int index) { @@ -71,7 +65,7 @@ private static long evalValue(LongVector container, int index) { public Block evalBlock(Block b) { LongBlock block = (LongBlock) b; int positionCount = block.getPositionCount(); - LongBlock.Builder builder = LongBlock.newBlockBuilder(positionCount); + LongBlock.Builder builder = LongBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); for (int p = 0; p < positionCount; p++) { int valueCount = block.getValueCount(p); int start = block.getFirstValueIndex(p); diff --git a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToUnsignedLongFromStringEvaluator.java b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToUnsignedLongFromStringEvaluator.java index 3297fcffbe73b..38056be01487c 100644 --- a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToUnsignedLongFromStringEvaluator.java +++ b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToUnsignedLongFromStringEvaluator.java @@ -6,16 +6,13 @@ import java.lang.Override; import java.lang.String; -import java.util.BitSet; import org.apache.lucene.util.BytesRef; import org.elasticsearch.compute.data.Block; import org.elasticsearch.compute.data.BytesRefBlock; import org.elasticsearch.compute.data.BytesRefVector; -import org.elasticsearch.compute.data.ConstantLongVector; -import org.elasticsearch.compute.data.LongArrayBlock; -import org.elasticsearch.compute.data.LongArrayVector; import org.elasticsearch.compute.data.LongBlock; import org.elasticsearch.compute.data.Vector; +import org.elasticsearch.compute.operator.DriverContext; import org.elasticsearch.compute.operator.EvalOperator; import org.elasticsearch.xpack.ql.tree.Source; @@ -24,8 +21,12 @@ * This class is generated. Do not edit it. */ public final class ToUnsignedLongFromStringEvaluator extends AbstractConvertFunction.AbstractEvaluator { - public ToUnsignedLongFromStringEvaluator(EvalOperator.ExpressionEvaluator field, Source source) { + private final DriverContext driverContext; + + public ToUnsignedLongFromStringEvaluator(EvalOperator.ExpressionEvaluator field, Source source, + DriverContext driverContext) { super(field, source); + this.driverContext = driverContext; } @Override @@ -40,29 +41,22 @@ public Block evalVector(Vector v) { BytesRef scratchPad = new BytesRef(); if (vector.isConstant()) { try { - return new ConstantLongVector(evalValue(vector, 0, scratchPad), positionCount).asBlock(); + return driverContext.blockFactory().newConstantLongBlockWith(evalValue(vector, 0, scratchPad), positionCount); } catch (Exception e) { registerException(e); - return Block.constantNullBlock(positionCount); + return Block.constantNullBlock(positionCount, driverContext.blockFactory()); } } - BitSet nullsMask = null; - long[] values = new long[positionCount]; + LongBlock.Builder builder = LongBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); for (int p = 0; p < positionCount; p++) { try { - values[p] = evalValue(vector, p, scratchPad); + builder.appendLong(evalValue(vector, p, scratchPad)); } catch (Exception e) { registerException(e); - if (nullsMask == null) { - nullsMask = new BitSet(positionCount); - } - nullsMask.set(p); + builder.appendNull(); } } - return nullsMask == null - ? new LongArrayVector(values, positionCount).asBlock() - // UNORDERED, since whatever ordering there is, it isn't necessarily preserved - : new LongArrayBlock(values, positionCount, null, nullsMask, Block.MvOrdering.UNORDERED); + return builder.build(); } private static long evalValue(BytesRefVector container, int index, BytesRef scratchPad) { @@ -74,7 +68,7 @@ private static long evalValue(BytesRefVector container, int index, BytesRef scra public Block evalBlock(Block b) { BytesRefBlock block = (BytesRefBlock) b; int positionCount = block.getPositionCount(); - LongBlock.Builder builder = LongBlock.newBlockBuilder(positionCount); + LongBlock.Builder builder = LongBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); BytesRef scratchPad = new BytesRef(); for (int p = 0; p < positionCount; p++) { int valueCount = block.getValueCount(p); diff --git a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToVersionFromStringEvaluator.java b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToVersionFromStringEvaluator.java index 5f6b62e16de52..bead25f13dd6a 100644 --- a/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToVersionFromStringEvaluator.java +++ b/x-pack/plugin/esql/src/main/java/generated/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToVersionFromStringEvaluator.java @@ -6,17 +6,12 @@ import java.lang.Override; import java.lang.String; -import java.util.BitSet; import org.apache.lucene.util.BytesRef; -import org.elasticsearch.common.util.BigArrays; -import org.elasticsearch.common.util.BytesRefArray; import org.elasticsearch.compute.data.Block; -import org.elasticsearch.compute.data.BytesRefArrayBlock; -import org.elasticsearch.compute.data.BytesRefArrayVector; import org.elasticsearch.compute.data.BytesRefBlock; import org.elasticsearch.compute.data.BytesRefVector; -import org.elasticsearch.compute.data.ConstantBytesRefVector; import org.elasticsearch.compute.data.Vector; +import org.elasticsearch.compute.operator.DriverContext; import org.elasticsearch.compute.operator.EvalOperator; import org.elasticsearch.xpack.ql.tree.Source; @@ -25,8 +20,12 @@ * This class is generated. Do not edit it. */ public final class ToVersionFromStringEvaluator extends AbstractConvertFunction.AbstractEvaluator { - public ToVersionFromStringEvaluator(EvalOperator.ExpressionEvaluator field, Source source) { + private final DriverContext driverContext; + + public ToVersionFromStringEvaluator(EvalOperator.ExpressionEvaluator field, Source source, + DriverContext driverContext) { super(field, source); + this.driverContext = driverContext; } @Override @@ -41,30 +40,22 @@ public Block evalVector(Vector v) { BytesRef scratchPad = new BytesRef(); if (vector.isConstant()) { try { - return new ConstantBytesRefVector(evalValue(vector, 0, scratchPad), positionCount).asBlock(); + return driverContext.blockFactory().newConstantBytesRefBlockWith(evalValue(vector, 0, scratchPad), positionCount); } catch (Exception e) { registerException(e); - return Block.constantNullBlock(positionCount); + return Block.constantNullBlock(positionCount, driverContext.blockFactory()); } } - BitSet nullsMask = null; - BytesRefArray values = new BytesRefArray(positionCount, BigArrays.NON_RECYCLING_INSTANCE); + BytesRefBlock.Builder builder = BytesRefBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); for (int p = 0; p < positionCount; p++) { try { - values.append(evalValue(vector, p, scratchPad)); + builder.appendBytesRef(evalValue(vector, p, scratchPad)); } catch (Exception e) { registerException(e); - if (nullsMask == null) { - nullsMask = new BitSet(positionCount); - } - nullsMask.set(p); - values.append(BytesRefBlock.NULL_VALUE); + builder.appendNull(); } } - return nullsMask == null - ? new BytesRefArrayVector(values, positionCount).asBlock() - // UNORDERED, since whatever ordering there is, it isn't necessarily preserved - : new BytesRefArrayBlock(values, positionCount, null, nullsMask, Block.MvOrdering.UNORDERED); + return builder.build(); } private static BytesRef evalValue(BytesRefVector container, int index, BytesRef scratchPad) { @@ -76,7 +67,7 @@ private static BytesRef evalValue(BytesRefVector container, int index, BytesRef public Block evalBlock(Block b) { BytesRefBlock block = (BytesRefBlock) b; int positionCount = block.getPositionCount(); - BytesRefBlock.Builder builder = BytesRefBlock.newBlockBuilder(positionCount); + BytesRefBlock.Builder builder = BytesRefBlock.newBlockBuilder(positionCount, driverContext.blockFactory()); BytesRef scratchPad = new BytesRef(); for (int p = 0; p < positionCount; p++) { int valueCount = block.getValueCount(p); diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/AbstractConvertFunction.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/AbstractConvertFunction.java index 1af66cb4f50b0..de24b049ea575 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/AbstractConvertFunction.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/AbstractConvertFunction.java @@ -9,9 +9,11 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.elasticsearch.common.TriFunction; import org.elasticsearch.compute.data.Block; import org.elasticsearch.compute.data.Page; import org.elasticsearch.compute.data.Vector; +import org.elasticsearch.compute.operator.DriverContext; import org.elasticsearch.compute.operator.EvalOperator; import org.elasticsearch.compute.operator.EvalOperator.ExpressionEvaluator; import org.elasticsearch.core.Releasables; @@ -25,7 +27,6 @@ import java.util.Locale; import java.util.Map; -import java.util.function.BiFunction; import java.util.function.Function; import static org.elasticsearch.xpack.ql.expression.TypeResolutions.isType; @@ -48,7 +49,7 @@ protected ExpressionEvaluator.Factory evaluator(ExpressionEvaluator.Factory fiel if (evaluator == null) { throw EsqlIllegalArgumentException.illegalDataType(sourceType); } - return dvrCtx -> evaluator.apply(fieldEval.get(dvrCtx), source()); + return dvrCtx -> evaluator.apply(fieldEval.get(dvrCtx), source(), dvrCtx); } @Override @@ -65,7 +66,7 @@ protected final TypeResolution resolveType() { ); } - protected abstract Map> evaluators(); + protected abstract Map> evaluators(); @Override public final Object fold() { diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToBoolean.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToBoolean.java index 3ec6492ef0d8c..701b3fa67732c 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToBoolean.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToBoolean.java @@ -8,7 +8,9 @@ package org.elasticsearch.xpack.esql.expression.function.scalar.convert; import org.apache.lucene.util.BytesRef; +import org.elasticsearch.common.TriFunction; import org.elasticsearch.compute.ann.ConvertEvaluator; +import org.elasticsearch.compute.operator.DriverContext; import org.elasticsearch.compute.operator.EvalOperator; import org.elasticsearch.xpack.ql.expression.Expression; import org.elasticsearch.xpack.ql.tree.NodeInfo; @@ -18,7 +20,6 @@ import java.math.BigInteger; import java.util.List; import java.util.Map; -import java.util.function.BiFunction; import static org.elasticsearch.xpack.ql.type.DataTypes.BOOLEAN; import static org.elasticsearch.xpack.ql.type.DataTypes.DOUBLE; @@ -30,10 +31,11 @@ public class ToBoolean extends AbstractConvertFunction { - private static final Map> EVALUATORS = - Map.of( + private static final Map< + DataType, + TriFunction> EVALUATORS = Map.of( BOOLEAN, - (fieldEval, source) -> fieldEval, + (fieldEval, source, driverContext) -> fieldEval, KEYWORD, ToBooleanFromStringEvaluator::new, DOUBLE, @@ -51,7 +53,9 @@ public ToBoolean(Source source, Expression field) { } @Override - protected Map> evaluators() { + protected + Map> + evaluators() { return EVALUATORS; } diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToDatetime.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToDatetime.java index 5049a80d075f9..eb23e460b88ff 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToDatetime.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToDatetime.java @@ -8,7 +8,9 @@ package org.elasticsearch.xpack.esql.expression.function.scalar.convert; import org.apache.lucene.util.BytesRef; +import org.elasticsearch.common.TriFunction; import org.elasticsearch.compute.ann.ConvertEvaluator; +import org.elasticsearch.compute.operator.DriverContext; import org.elasticsearch.compute.operator.EvalOperator; import org.elasticsearch.xpack.esql.expression.function.scalar.date.DateParse; import org.elasticsearch.xpack.ql.expression.Expression; @@ -18,7 +20,6 @@ import java.util.List; import java.util.Map; -import java.util.function.BiFunction; import static org.elasticsearch.xpack.ql.type.DataTypes.DATETIME; import static org.elasticsearch.xpack.ql.type.DataTypes.DOUBLE; @@ -29,12 +30,13 @@ public class ToDatetime extends AbstractConvertFunction { - private static final Map> EVALUATORS = - Map.of( + private static final Map< + DataType, + TriFunction> EVALUATORS = Map.of( DATETIME, - (fieldEval, source) -> fieldEval, + (fieldEval, source, driverContext) -> fieldEval, LONG, - (fieldEval, source) -> fieldEval, + (fieldEval, source, driverContext) -> fieldEval, KEYWORD, ToDatetimeFromStringEvaluator::new, DOUBLE, @@ -50,7 +52,9 @@ public ToDatetime(Source source, Expression field) { } @Override - protected Map> evaluators() { + protected + Map> + evaluators() { return EVALUATORS; } diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToDegrees.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToDegrees.java index ec59446989bca..299e8cfe8643e 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToDegrees.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToDegrees.java @@ -7,7 +7,9 @@ package org.elasticsearch.xpack.esql.expression.function.scalar.convert; +import org.elasticsearch.common.TriFunction; import org.elasticsearch.compute.ann.ConvertEvaluator; +import org.elasticsearch.compute.operator.DriverContext; import org.elasticsearch.compute.operator.EvalOperator; import org.elasticsearch.xpack.esql.evaluator.mapper.EvaluatorMapper; import org.elasticsearch.xpack.ql.expression.Expression; @@ -17,7 +19,6 @@ import java.util.List; import java.util.Map; -import java.util.function.BiFunction; import static org.elasticsearch.xpack.ql.type.DataTypes.DOUBLE; import static org.elasticsearch.xpack.ql.type.DataTypes.INTEGER; @@ -29,16 +30,29 @@ * to degrees. */ public class ToDegrees extends AbstractConvertFunction implements EvaluatorMapper { - private static final Map> EVALUATORS = - Map.of( + private static final Map< + DataType, + TriFunction> EVALUATORS = Map.of( DOUBLE, ToDegreesEvaluator::new, INTEGER, - (field, source) -> new ToDegreesEvaluator(new ToDoubleFromIntEvaluator(field, source), source), + (field, source, driverContext) -> new ToDegreesEvaluator( + new ToDoubleFromIntEvaluator(field, source, driverContext), + source, + driverContext + ), LONG, - (field, source) -> new ToDegreesEvaluator(new ToDoubleFromLongEvaluator(field, source), source), + (field, source, driverContext) -> new ToDegreesEvaluator( + new ToDoubleFromLongEvaluator(field, source, driverContext), + source, + driverContext + ), UNSIGNED_LONG, - (field, source) -> new ToDegreesEvaluator(new ToDoubleFromUnsignedLongEvaluator(field, source), source) + (field, source, driverContext) -> new ToDegreesEvaluator( + new ToDoubleFromUnsignedLongEvaluator(field, source, driverContext), + source, + driverContext + ) ); public ToDegrees(Source source, Expression field) { @@ -46,7 +60,9 @@ public ToDegrees(Source source, Expression field) { } @Override - protected Map> evaluators() { + protected + Map> + evaluators() { return EVALUATORS; } diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToDouble.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToDouble.java index dc8527637c7a3..690f7a66cbece 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToDouble.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToDouble.java @@ -8,7 +8,9 @@ package org.elasticsearch.xpack.esql.expression.function.scalar.convert; import org.apache.lucene.util.BytesRef; +import org.elasticsearch.common.TriFunction; import org.elasticsearch.compute.ann.ConvertEvaluator; +import org.elasticsearch.compute.operator.DriverContext; import org.elasticsearch.compute.operator.EvalOperator; import org.elasticsearch.xpack.ql.expression.Expression; import org.elasticsearch.xpack.ql.tree.NodeInfo; @@ -17,7 +19,6 @@ import java.util.List; import java.util.Map; -import java.util.function.BiFunction; import static org.elasticsearch.xpack.ql.type.DataTypes.BOOLEAN; import static org.elasticsearch.xpack.ql.type.DataTypes.DATETIME; @@ -30,10 +31,11 @@ public class ToDouble extends AbstractConvertFunction { - private static final Map> EVALUATORS = - Map.of( + private static final Map< + DataType, + TriFunction> EVALUATORS = Map.of( DOUBLE, - (fieldEval, source) -> fieldEval, + (fieldEval, source, driverContext) -> fieldEval, BOOLEAN, ToDoubleFromBooleanEvaluator::new, DATETIME, @@ -53,7 +55,9 @@ public ToDouble(Source source, Expression field) { } @Override - protected Map> evaluators() { + protected + Map> + evaluators() { return EVALUATORS; } diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToIP.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToIP.java index 0931033758dbb..d55b9d23975e1 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToIP.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToIP.java @@ -8,7 +8,9 @@ package org.elasticsearch.xpack.esql.expression.function.scalar.convert; import org.apache.lucene.util.BytesRef; +import org.elasticsearch.common.TriFunction; import org.elasticsearch.compute.ann.ConvertEvaluator; +import org.elasticsearch.compute.operator.DriverContext; import org.elasticsearch.compute.operator.EvalOperator; import org.elasticsearch.xpack.ql.expression.Expression; import org.elasticsearch.xpack.ql.tree.NodeInfo; @@ -17,7 +19,6 @@ import java.util.List; import java.util.Map; -import java.util.function.BiFunction; import static org.elasticsearch.xpack.ql.type.DataTypes.IP; import static org.elasticsearch.xpack.ql.type.DataTypes.KEYWORD; @@ -25,15 +26,23 @@ public class ToIP extends AbstractConvertFunction { - private static final Map> EVALUATORS = - Map.of(IP, (fieldEval, source) -> fieldEval, KEYWORD, ToIPFromStringEvaluator::new); + private static final Map< + DataType, + TriFunction> EVALUATORS = Map.of( + IP, + (fieldEval, source, driverContext) -> fieldEval, + KEYWORD, + ToIPFromStringEvaluator::new + ); public ToIP(Source source, Expression field) { super(source, field); } @Override - protected Map> evaluators() { + protected + Map> + evaluators() { return EVALUATORS; } diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToInteger.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToInteger.java index 1d26c4724a423..0fcf62ed3864a 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToInteger.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToInteger.java @@ -8,7 +8,9 @@ package org.elasticsearch.xpack.esql.expression.function.scalar.convert; import org.apache.lucene.util.BytesRef; +import org.elasticsearch.common.TriFunction; import org.elasticsearch.compute.ann.ConvertEvaluator; +import org.elasticsearch.compute.operator.DriverContext; import org.elasticsearch.compute.operator.EvalOperator; import org.elasticsearch.xpack.ql.expression.Expression; import org.elasticsearch.xpack.ql.tree.NodeInfo; @@ -17,7 +19,6 @@ import java.util.List; import java.util.Map; -import java.util.function.BiFunction; import static org.elasticsearch.xpack.ql.type.DataTypeConverter.safeDoubleToLong; import static org.elasticsearch.xpack.ql.type.DataTypeConverter.safeToInt; @@ -31,10 +32,11 @@ public class ToInteger extends AbstractConvertFunction { - private static final Map> EVALUATORS = - Map.of( + private static final Map< + DataType, + TriFunction> EVALUATORS = Map.of( INTEGER, - (fieldEval, source) -> fieldEval, + (fieldEval, source, driverContext) -> fieldEval, BOOLEAN, ToIntegerFromBooleanEvaluator::new, DATETIME, @@ -54,7 +56,9 @@ public ToInteger(Source source, Expression field) { } @Override - protected Map> evaluators() { + protected + Map> + evaluators() { return EVALUATORS; } diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToLong.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToLong.java index ffb31a77cb1fc..8e50dd8540ffd 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToLong.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToLong.java @@ -8,7 +8,9 @@ package org.elasticsearch.xpack.esql.expression.function.scalar.convert; import org.apache.lucene.util.BytesRef; +import org.elasticsearch.common.TriFunction; import org.elasticsearch.compute.ann.ConvertEvaluator; +import org.elasticsearch.compute.operator.DriverContext; import org.elasticsearch.compute.operator.EvalOperator; import org.elasticsearch.xpack.ql.expression.Expression; import org.elasticsearch.xpack.ql.tree.NodeInfo; @@ -17,7 +19,6 @@ import java.util.List; import java.util.Map; -import java.util.function.BiFunction; import static org.elasticsearch.xpack.ql.type.DataTypeConverter.safeDoubleToLong; import static org.elasticsearch.xpack.ql.type.DataTypeConverter.safeToLong; @@ -32,12 +33,13 @@ public class ToLong extends AbstractConvertFunction { - private static final Map> EVALUATORS = - Map.of( + private static final Map< + DataType, + TriFunction> EVALUATORS = Map.of( LONG, - (fieldEval, source) -> fieldEval, + (fieldEval, source, driverContext) -> fieldEval, DATETIME, - (fieldEval, source) -> fieldEval, + (fieldEval, source, driverContext) -> fieldEval, BOOLEAN, ToLongFromBooleanEvaluator::new, KEYWORD, @@ -55,7 +57,9 @@ public ToLong(Source source, Expression field) { } @Override - protected Map> evaluators() { + protected + Map> + evaluators() { return EVALUATORS; } diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToRadians.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToRadians.java index 8064303e204d5..8bb5180e09752 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToRadians.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToRadians.java @@ -7,7 +7,9 @@ package org.elasticsearch.xpack.esql.expression.function.scalar.convert; +import org.elasticsearch.common.TriFunction; import org.elasticsearch.compute.ann.ConvertEvaluator; +import org.elasticsearch.compute.operator.DriverContext; import org.elasticsearch.compute.operator.EvalOperator; import org.elasticsearch.xpack.esql.evaluator.mapper.EvaluatorMapper; import org.elasticsearch.xpack.ql.expression.Expression; @@ -17,7 +19,6 @@ import java.util.List; import java.util.Map; -import java.util.function.BiFunction; import static org.elasticsearch.xpack.ql.type.DataTypes.DOUBLE; import static org.elasticsearch.xpack.ql.type.DataTypes.INTEGER; @@ -29,16 +30,29 @@ * to radians. */ public class ToRadians extends AbstractConvertFunction implements EvaluatorMapper { - private static final Map> EVALUATORS = - Map.of( + private static final Map< + DataType, + TriFunction> EVALUATORS = Map.of( DOUBLE, ToRadiansEvaluator::new, INTEGER, - (field, source) -> new ToRadiansEvaluator(new ToDoubleFromIntEvaluator(field, source), source), + (field, source, driverContext) -> new ToRadiansEvaluator( + new ToDoubleFromIntEvaluator(field, source, driverContext), + source, + driverContext + ), LONG, - (field, source) -> new ToRadiansEvaluator(new ToDoubleFromLongEvaluator(field, source), source), + (field, source, driverContext) -> new ToRadiansEvaluator( + new ToDoubleFromLongEvaluator(field, source, driverContext), + source, + driverContext + ), UNSIGNED_LONG, - (field, source) -> new ToRadiansEvaluator(new ToDoubleFromUnsignedLongEvaluator(field, source), source) + (field, source, driverContext) -> new ToRadiansEvaluator( + new ToDoubleFromUnsignedLongEvaluator(field, source, driverContext), + source, + driverContext + ) ); public ToRadians(Source source, Expression field) { @@ -46,7 +60,9 @@ public ToRadians(Source source, Expression field) { } @Override - protected Map> evaluators() { + protected + Map> + evaluators() { return EVALUATORS; } diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToString.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToString.java index 428c1f32b1fc7..af895ab7c56cf 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToString.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToString.java @@ -8,7 +8,9 @@ package org.elasticsearch.xpack.esql.expression.function.scalar.convert; import org.apache.lucene.util.BytesRef; +import org.elasticsearch.common.TriFunction; import org.elasticsearch.compute.ann.ConvertEvaluator; +import org.elasticsearch.compute.operator.DriverContext; import org.elasticsearch.compute.operator.EvalOperator; import org.elasticsearch.search.DocValueFormat; import org.elasticsearch.xpack.esql.evaluator.mapper.EvaluatorMapper; @@ -21,7 +23,6 @@ import java.util.List; import java.util.Map; -import java.util.function.BiFunction; import static org.elasticsearch.xpack.ql.type.DataTypes.BOOLEAN; import static org.elasticsearch.xpack.ql.type.DataTypes.DATETIME; @@ -38,10 +39,11 @@ public class ToString extends AbstractConvertFunction implements EvaluatorMapper { - private static final Map> EVALUATORS = - Map.of( + private static final Map< + DataType, + TriFunction> EVALUATORS = Map.of( KEYWORD, - (fieldEval, source) -> fieldEval, + (fieldEval, source, driverContext) -> fieldEval, BOOLEAN, ToStringFromBooleanEvaluator::new, DATETIME, @@ -55,7 +57,7 @@ public class ToString extends AbstractConvertFunction implements EvaluatorMapper INTEGER, ToStringFromIntEvaluator::new, TEXT, - (fieldEval, source) -> fieldEval, + (fieldEval, source, driverContext) -> fieldEval, VERSION, ToStringFromVersionEvaluator::new, UNSIGNED_LONG, @@ -67,7 +69,9 @@ public ToString(Source source, @Named("v") Expression v) { } @Override - protected Map> evaluators() { + protected + Map> + evaluators() { return EVALUATORS; } diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToUnsignedLong.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToUnsignedLong.java index 83deed6b18490..396aa03f39dc6 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToUnsignedLong.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToUnsignedLong.java @@ -8,7 +8,9 @@ package org.elasticsearch.xpack.esql.expression.function.scalar.convert; import org.apache.lucene.util.BytesRef; +import org.elasticsearch.common.TriFunction; import org.elasticsearch.compute.ann.ConvertEvaluator; +import org.elasticsearch.compute.operator.DriverContext; import org.elasticsearch.compute.operator.EvalOperator; import org.elasticsearch.xpack.ql.expression.Expression; import org.elasticsearch.xpack.ql.tree.NodeInfo; @@ -17,7 +19,6 @@ import java.util.List; import java.util.Map; -import java.util.function.BiFunction; import static org.elasticsearch.xpack.ql.type.DataTypeConverter.safeToUnsignedLong; import static org.elasticsearch.xpack.ql.type.DataTypes.BOOLEAN; @@ -33,10 +34,11 @@ public class ToUnsignedLong extends AbstractConvertFunction { - private static final Map> EVALUATORS = - Map.of( + private static final Map< + DataType, + TriFunction> EVALUATORS = Map.of( UNSIGNED_LONG, - (fieldEval, source) -> fieldEval, + (fieldEval, source, driverContext) -> fieldEval, DATETIME, ToUnsignedLongFromLongEvaluator::new, BOOLEAN, @@ -56,7 +58,9 @@ public ToUnsignedLong(Source source, Expression field) { } @Override - protected Map> evaluators() { + protected + Map> + evaluators() { return EVALUATORS; } diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToVersion.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToVersion.java index 0051bee45eead..559e2fc4f89fa 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToVersion.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/expression/function/scalar/convert/ToVersion.java @@ -8,7 +8,9 @@ package org.elasticsearch.xpack.esql.expression.function.scalar.convert; import org.apache.lucene.util.BytesRef; +import org.elasticsearch.common.TriFunction; import org.elasticsearch.compute.ann.ConvertEvaluator; +import org.elasticsearch.compute.operator.DriverContext; import org.elasticsearch.compute.operator.EvalOperator; import org.elasticsearch.xpack.esql.expression.function.Named; import org.elasticsearch.xpack.ql.expression.Expression; @@ -19,7 +21,6 @@ import java.util.List; import java.util.Map; -import java.util.function.BiFunction; import static org.elasticsearch.xpack.ql.type.DataTypes.KEYWORD; import static org.elasticsearch.xpack.ql.type.DataTypes.TEXT; @@ -27,9 +28,10 @@ public class ToVersion extends AbstractConvertFunction { - private static final Map> EVALUATORS = - Map.ofEntries( - Map.entry(VERSION, (fieldEval, source) -> fieldEval), + private static final Map< + DataType, + TriFunction> EVALUATORS = Map.ofEntries( + Map.entry(VERSION, (fieldEval, source, driverContext) -> fieldEval), Map.entry(KEYWORD, ToVersionFromStringEvaluator::new), Map.entry(TEXT, ToVersionFromStringEvaluator::new) ); @@ -39,7 +41,9 @@ public ToVersion(Source source, @Named("v") Expression v) { } @Override - protected Map> evaluators() { + protected + Map> + evaluators() { return EVALUATORS; } From c2072fc4d369d76c2f78024dcc31548ec4789721 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Istv=C3=A1n=20Zolt=C3=A1n=20Szab=C3=B3?= Date: Fri, 29 Sep 2023 10:12:07 +0200 Subject: [PATCH 31/32] [DOCS] Adds reference documentation for inference API (#99658) * [DOCS] Creates documentation structure. * [DOCS] Adds PUT inference API docs and part of GET inference API docs. * [DOCS] Fixes complaining CI. * [DOCS] Adds GET and DELETE API docs for inference API. * [DOCS] Adds POST inference API docs. * Apply suggestions from code review --- .../inference/delete-inference.asciidoc | 57 ++++++++++ .../inference/get-inference.asciidoc | 79 +++++++++++++ .../inference/inference-apis.asciidoc | 16 +++ .../inference/post-inference.asciidoc | 97 ++++++++++++++++ .../inference/put-inference.asciidoc | 104 ++++++++++++++++++ docs/reference/rest-api/index.asciidoc | 6 +- 6 files changed, 357 insertions(+), 2 deletions(-) create mode 100644 docs/reference/inference/delete-inference.asciidoc create mode 100644 docs/reference/inference/get-inference.asciidoc create mode 100644 docs/reference/inference/inference-apis.asciidoc create mode 100644 docs/reference/inference/post-inference.asciidoc create mode 100644 docs/reference/inference/put-inference.asciidoc diff --git a/docs/reference/inference/delete-inference.asciidoc b/docs/reference/inference/delete-inference.asciidoc new file mode 100644 index 0000000000000..874bfa64d3551 --- /dev/null +++ b/docs/reference/inference/delete-inference.asciidoc @@ -0,0 +1,57 @@ +[role="xpack"] +[[delete-inference-api]] +=== Delete {infer} API + +Deletes an {infer} model deployment. + + +[discrete] +[[delete-inference-api-request]] +==== {api-request-title} + +`DELETE /_inference//` + +[discrete] +[[delete-inference-api-prereqs]] +==== {api-prereq-title} + +* Requires the `manage` <>. + + +[discrete] +[[delete-inference-api-path-params]] +==== {api-path-parms-title} + +:: +(Required, string) +The unique identifier of the {infer} model to delete. + +:: +(Required, string) +The type of {infer} task that the model performs. + + +[discrete] +[[delete-inference-api-example]] +==== {api-examples-title} + +The following API call deletes the `my-elser-model` {infer} model that can +perform `sparse_embedding` tasks. + + +[source,console] +------------------------------------------------------------ +DELETE /_inference/sparse_embedding/my-elser-model +------------------------------------------------------------ +// TEST[skip:TBD] + + +The API returns the following response: + +[source,console-result] +------------------------------------------------------------ +{ + "acknowledged": true +} +------------------------------------------------------------ +// NOTCONSOLE \ No newline at end of file diff --git a/docs/reference/inference/get-inference.asciidoc b/docs/reference/inference/get-inference.asciidoc new file mode 100644 index 0000000000000..7e32bd05b5f56 --- /dev/null +++ b/docs/reference/inference/get-inference.asciidoc @@ -0,0 +1,79 @@ +[role="xpack"] +[[get-inference-api]] +=== Get {infer} API + +Retrieves {infer} model information. + +[discrete] +[[get-inference-api-request]] +==== {api-request-title} + +`GET /_inference/_all` + +`GET /_inference//_all` + +`GET /_inference//` + +[discrete] +[[get-inference-api-prereqs]] +==== {api-prereq-title} + +* Requires the `manage` <>. + +[discrete] +[[get-inference-api-desc]] +==== {api-description-title} + +You can get information in a single API request for: + +* a single {infer} model by providing the task type and the model ID, +* all of the {infer} models for a certain task type by providing the task type +and a wildcard expression, +* all of the {infer} models by using a wildcard expression. + + +[discrete] +[[get-inference-api-path-params]] +==== {api-path-parms-title} + +``:: +(Optional, string) +The unique identifier of the {infer} model. + + +``:: +(Optional, string) +The type of {infer} task that the model performs. + + +[discrete] +[[get-inference-api-example]] +==== {api-examples-title} + +The following API call retrives information about the `my-elser-model` {infer} +model that can perform `sparse_embedding` tasks. + + +[source,console] +------------------------------------------------------------ +GET _inference/sparse_embedding/my-elser-model +------------------------------------------------------------ +// TEST[skip:TBD] + + +The API returns the following response: + +[source,console-result] +------------------------------------------------------------ +{ + "model_id": "my-elser-model", + "task_type": "sparse_embedding", + "service": "elser_mlnode", + "service_settings": { + "num_allocations": 1, + "num_threads": 1 + }, + "task_settings": {} +} +------------------------------------------------------------ +// NOTCONSOLE \ No newline at end of file diff --git a/docs/reference/inference/inference-apis.asciidoc b/docs/reference/inference/inference-apis.asciidoc new file mode 100644 index 0000000000000..ec1f01bc4d093 --- /dev/null +++ b/docs/reference/inference/inference-apis.asciidoc @@ -0,0 +1,16 @@ +[role="xpack"] +[[inference-apis]] +== {infer-cap} APIs + +You can use the following APIs to manage {infer} models and perform {infer}: + +* <> +* <> +* <> +* <> + + +include::delete-inference.asciidoc[] +include::get-inference.asciidoc[] +include::post-inference.asciidoc[] +include::put-inference.asciidoc[] \ No newline at end of file diff --git a/docs/reference/inference/post-inference.asciidoc b/docs/reference/inference/post-inference.asciidoc new file mode 100644 index 0000000000000..99dd4a059519f --- /dev/null +++ b/docs/reference/inference/post-inference.asciidoc @@ -0,0 +1,97 @@ +[role="xpack"] +[[post-inference-api]] +=== Perform inference API + +Performs an inference task on an input text by using an {infer} model. + + +[discrete] +[[post-inference-api-request]] +==== {api-request-title} + +`POST /_inference//` + + +[discrete] +[[post-inference-api-prereqs]] +==== {api-prereq-title} + +* Requires the `manage` <>. + + +[discrete] +[[post-inference-api-desc]] +==== {api-description-title} + +The perform {infer} API enables you to use {infer} models to perform specific +tasks on data that you provide as an input. The API returns a response with the +resutls of the tasks. The {infer} model you use can perform one specific task +that has been defined when the model was created with the <>. + + +[discrete] +[[post-inference-api-path-params]] +==== {api-path-parms-title} + +``:: +(Required, string) +The unique identifier of the {infer} model. + + +``:: +(Required, string) +The type of {infer} task that the model performs. + + +[discrete] +[[post-inference-api-request-body]] +== {api-request-body-title} + +`input`:: +(Required, string) +The text on which you want to perform the {infer} task. + + +[discrete] +[[post-inference-api-example]] +==== {api-examples-title} + +The following example performs sparse embedding on the example sentence. + + +[source,console] +------------------------------------------------------------ +POST _inference/sparse_embedding/my-elser-model +{ + "input": "The sky above the port was the color of television tuned to a dead channel." +} +------------------------------------------------------------ +// TEST[skip:TBD] + + +The API returns the following response: + + +[source,console-result] +------------------------------------------------------------ +{ + "sparse_embedding": { + "port": 2.1259406, + "sky": 1.7073475, + "color": 1.6922266, + "dead": 1.6247464, + "television": 1.3525393, + "above": 1.2425821, + "tuned": 1.1440028, + "colors": 1.1218185, + "tv": 1.0111054, + "ports": 1.0067928, + "poem": 1.0042328, + "channel": 0.99471164, + "tune": 0.96235967, + "scene": 0.9020516, + (...) + } +} +------------------------------------------------------------ +// NOTCONSOLE \ No newline at end of file diff --git a/docs/reference/inference/put-inference.asciidoc b/docs/reference/inference/put-inference.asciidoc new file mode 100644 index 0000000000000..c5ccd6a57a8dd --- /dev/null +++ b/docs/reference/inference/put-inference.asciidoc @@ -0,0 +1,104 @@ +[role="xpack"] +[[put-inference-api]] +=== Create {infer} API + +Creates a model to perform an {infer} task. + + +[discrete] +[[put-inference-api-request]] +==== {api-request-title} + +`PUT /_inference//` + + +[discrete] +[[put-inference-api-prereqs]] +==== {api-prereq-title} + +* Requires the `manage` <>. + +[discrete] +[[put-inference-api-desc]] +==== {api-description-title} + +The create {infer} API enables you to create and configure an {infer} model to +perform a specific {infer} task. + + +[discrete] +[[put-inference-api-path-params]] +==== {api-path-parms-title} + + +``:: +(Required, string) +The unique identifier of the model. + +``:: +(Required, string) +The type of the {infer} task that the model will perform. Available task types: +* `sparse_embedding`, +* `text_embedding`. + + +[discrete] +[[put-inference-api-request-body]] +== {api-request-body-title} + +`service`:: +(Required, string) +The type of service supported for the specified task type. +Available services: +* `elser`, +* `elser_mlnode`. + +`service_settings`:: +(Required, object) +Settings used to install the {infer} model. These settings are specific to the +`service` you specified. + +`task_settings`:: +(Optional, object) +Settings to configure the {infer} task. These settings are specific to the +`` you specified. + + +[discrete] +[[put-inference-api-example]] +==== {api-examples-title} + +The following example shows how to create an {infer} model called +`my-elser-model` to perform a `sparse_embedding` task type. + +[source,console] +------------------------------------------------------------ +PUT _inference/sparse_embedding/my-elser-model +{ + "service": "elser_mlnode", + "service_settings": { + "num_allocations": 1, + "num_threads": 1 + }, + "task_settings": {} +} +------------------------------------------------------------ +// TEST[skip:TBD] + + +Example response: + +[source,console-result] +------------------------------------------------------------ +{ + "model_id": "my-elser-model", + "task_type": "sparse_embedding", + "service": "elser_mlnode", + "service_settings": { + "num_allocations": 1, + "num_threads": 1 + }, + "task_settings": {} +} +------------------------------------------------------------ +// NOTCONSOLE diff --git a/docs/reference/rest-api/index.asciidoc b/docs/reference/rest-api/index.asciidoc index 1da39333db43e..b8ad9d9a0736e 100644 --- a/docs/reference/rest-api/index.asciidoc +++ b/docs/reference/rest-api/index.asciidoc @@ -28,8 +28,9 @@ not be included yet. * <> * <> * <> -* <> +* <> * <> +* <> * <> * <> * <> @@ -74,8 +75,9 @@ include::{es-repo-dir}/text-structure/apis/find-structure.asciidoc[leveloffset=+ include::{es-repo-dir}/graph/explore.asciidoc[] include::{es-repo-dir}/indices.asciidoc[] include::{es-repo-dir}/ilm/apis/ilm-api.asciidoc[] -include::{es-repo-dir}/ingest/apis/index.asciidoc[] +include::{es-repo-dir}/inference/inference-apis.asciidoc[] include::info.asciidoc[] +include::{es-repo-dir}/ingest/apis/index.asciidoc[] include::{es-repo-dir}/licensing/index.asciidoc[] include::{es-repo-dir}/rest-api/logstash/index.asciidoc[] include::{es-repo-dir}/ml/common/apis/index.asciidoc[] From e23fd3269c07e322f0fd112582edb6c131f93066 Mon Sep 17 00:00:00 2001 From: David Turner Date: Fri, 29 Sep 2023 09:45:33 +0100 Subject: [PATCH 32/32] Move SLM eligibility check (#100044) A small refactoring to make #99953 a little simpler: combine the logic for retrieving the snapshot info and filtering out the ineligible ones into a single function so we can replace it with a call to a dedicated client action in a followup. --- .../xpack/slm/SnapshotRetentionTask.java | 74 ++++++++++--------- .../xpack/slm/SnapshotRetentionTaskTests.java | 60 ++++++--------- 2 files changed, 63 insertions(+), 71 deletions(-) diff --git a/x-pack/plugin/slm/src/main/java/org/elasticsearch/xpack/slm/SnapshotRetentionTask.java b/x-pack/plugin/slm/src/main/java/org/elasticsearch/xpack/slm/SnapshotRetentionTask.java index ac1b2f30ec06d..708621ee4a6c8 100644 --- a/x-pack/plugin/slm/src/main/java/org/elasticsearch/xpack/slm/SnapshotRetentionTask.java +++ b/x-pack/plugin/slm/src/main/java/org/elasticsearch/xpack/slm/SnapshotRetentionTask.java @@ -91,15 +91,6 @@ public SnapshotRetentionTask( this.historyStore = historyStore; } - private static String formatSnapshots(Map> snapshotMap) { - return snapshotMap.entrySet() - .stream() - .map( - e -> e.getKey() + ": [" + e.getValue().stream().map(si -> si.snapshotId().getName()).collect(Collectors.joining(",")) + "]" - ) - .collect(Collectors.joining(",")); - } - @Override public void triggered(SchedulerEngine.Event event) { assert event.getJobName().equals(SnapshotRetentionService.SLM_RETENTION_JOB_ID) @@ -156,28 +147,9 @@ public void triggered(SchedulerEngine.Event event) { // Finally, asynchronously retrieve all the snapshots, deleting them serially, // before updating the cluster state with the new metrics and setting 'running' // back to false - getAllRetainableSnapshots(repositioriesToFetch, policiesWithRetention.keySet(), new ActionListener<>() { + getSnapshotsEligibleForDeletion(repositioriesToFetch, policiesWithRetention, new ActionListener<>() { @Override - public void onResponse(Map> allSnapshots) { - if (logger.isTraceEnabled()) { - logger.trace("retrieved snapshots: [{}]", formatSnapshots(allSnapshots)); - } - // Find all the snapshots that are past their retention date - final Map>> snapshotsToBeDeleted = allSnapshots.entrySet() - .stream() - .collect( - Collectors.toMap( - Map.Entry::getKey, - e -> e.getValue() - .stream() - .filter(snapshot -> snapshotEligibleForDeletion(snapshot, allSnapshots, policiesWithRetention)) - // SnapshotInfo instances can be quite large in case they contain e.g. a large collection of - // exceptions so we extract the only two things (id + policy id) here so they can be GCed - .map(snapshotInfo -> Tuple.tuple(snapshotInfo.snapshotId(), getPolicyId(snapshotInfo))) - .toList() - ) - ); - + public void onResponse(Map>> snapshotsToBeDeleted) { if (logger.isTraceEnabled()) { logger.trace("snapshots eligible for deletion: [{}]", snapshotsToBeDeleted); } @@ -256,10 +228,10 @@ static boolean snapshotEligibleForDeletion( return eligible; } - void getAllRetainableSnapshots( + void getSnapshotsEligibleForDeletion( Collection repositories, - Set policies, - ActionListener>> listener + Map policies, + ActionListener>>> listener ) { if (repositories.isEmpty()) { // Skip retrieving anything if there are no repositories to fetch @@ -273,7 +245,7 @@ void getAllRetainableSnapshots( // don't time out on this request to not produce failed SLM runs in case of a temporarily slow master node .setMasterNodeTimeout(TimeValue.MAX_VALUE) .setIgnoreUnavailable(true) - .setPolicies(policies.toArray(Strings.EMPTY_ARRAY)) + .setPolicies(policies.keySet().toArray(Strings.EMPTY_ARRAY)) .setIncludeIndexNames(false) .execute(ActionListener.wrap(resp -> { if (logger.isTraceEnabled()) { @@ -300,7 +272,39 @@ void getAllRetainableSnapshots( logger.debug(() -> "unable to retrieve snapshots for [" + repo + "] repositories: ", resp.getFailures().get(repo)); } } - listener.onResponse(snapshots); + + if (logger.isTraceEnabled()) { + logger.trace( + "retrieved snapshots: [{}]", + snapshots.entrySet() + .stream() + .map( + e -> e.getKey() + + ": [" + + e.getValue().stream().map(si -> si.snapshotId().getName()).collect(Collectors.joining(",")) + + "]" + ) + .collect(Collectors.joining(",")) + ); + } + + // Find all the snapshots that are past their retention date + final Map>> snapshotsToBeDeleted = snapshots.entrySet() + .stream() + .collect( + Collectors.toMap( + Map.Entry::getKey, + e -> e.getValue() + .stream() + .filter(snapshot -> snapshotEligibleForDeletion(snapshot, snapshots, policies)) + // SnapshotInfo instances can be quite large in case they contain e.g. a large collection of + // exceptions so we extract the only two things (id + policy id) here so they can be GCed + .map(snapshotInfo -> Tuple.tuple(snapshotInfo.snapshotId(), getPolicyId(snapshotInfo))) + .toList() + ) + ); + + listener.onResponse(snapshotsToBeDeleted); }, e -> { logger.debug(() -> "unable to retrieve snapshots for [" + repositories + "] repositories: ", e); listener.onFailure(e); diff --git a/x-pack/plugin/slm/src/test/java/org/elasticsearch/xpack/slm/SnapshotRetentionTaskTests.java b/x-pack/plugin/slm/src/test/java/org/elasticsearch/xpack/slm/SnapshotRetentionTaskTests.java index 15badabf3689a..b120b49c63654 100644 --- a/x-pack/plugin/slm/src/test/java/org/elasticsearch/xpack/slm/SnapshotRetentionTaskTests.java +++ b/x-pack/plugin/slm/src/test/java/org/elasticsearch/xpack/slm/SnapshotRetentionTaskTests.java @@ -26,6 +26,7 @@ import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.set.Sets; import org.elasticsearch.core.TimeValue; +import org.elasticsearch.core.Tuple; import org.elasticsearch.snapshots.Snapshot; import org.elasticsearch.snapshots.SnapshotId; import org.elasticsearch.snapshots.SnapshotInfo; @@ -42,7 +43,6 @@ import org.elasticsearch.xpack.core.slm.SnapshotRetentionConfiguration; import org.elasticsearch.xpack.slm.history.SnapshotHistoryStore; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -238,20 +238,6 @@ private void retentionTaskTest(final boolean deletionSuccess) throws Exception { 0L, Collections.emptyMap() ); - final SnapshotInfo ineligibleSnapshot = new SnapshotInfo( - new Snapshot(repoId, new SnapshotId("name2", "uuid2")), - Collections.singletonList("index"), - Collections.emptyList(), - Collections.emptyList(), - null, - System.currentTimeMillis() + 1, - 1, - Collections.emptyList(), - true, - Collections.singletonMap("policy", policyId), - System.currentTimeMillis(), - Collections.emptyMap() - ); Set deleted = ConcurrentHashMap.newKeySet(); Set deletedSnapshotsInHistory = ConcurrentHashMap.newKeySet(); @@ -273,11 +259,9 @@ private void retentionTaskTest(final boolean deletionSuccess) throws Exception { historyLatch.countDown(); }), () -> { - List snaps = new ArrayList<>(2); - snaps.add(eligibleSnapshot); - snaps.add(ineligibleSnapshot); - logger.info("--> retrieving snapshots [{}]", snaps); - return Collections.singletonMap(repoId, snaps); + final var result = Collections.singletonMap(repoId, List.of(Tuple.tuple(eligibleSnapshot.snapshotId(), policyId))); + logger.info("--> retrieving snapshots [{}]", result); + return result; }, (deletionPolicyId, repo, snapId, slmStats, listener) -> { logger.info("--> deleting {} from repo {}", snapId, repo); @@ -295,7 +279,7 @@ private void retentionTaskTest(final boolean deletionSuccess) throws Exception { long time = System.currentTimeMillis(); retentionTask.triggered(new SchedulerEngine.Event(SnapshotRetentionService.SLM_RETENTION_JOB_ID, time, time)); - deletionLatch.await(10, TimeUnit.SECONDS); + safeAwait(deletionLatch); assertThat("something should have been deleted", deleted, not(empty())); assertThat("one snapshot should have been deleted", deleted, hasSize(1)); @@ -364,18 +348,22 @@ protected void ); AtomicReference errHandlerCalled = new AtomicReference<>(null); - task.getAllRetainableSnapshots(Collections.singleton(repoId), Collections.singleton(policyId), new ActionListener<>() { - @Override - public void onResponse(Map> stringListMap) { - logger.info("--> forcing failure"); - throw new ElasticsearchException("forced failure"); - } + task.getSnapshotsEligibleForDeletion( + Collections.singleton(repoId), + Map.of(policyId, new SnapshotLifecyclePolicy(policyId, "test", "* * * * *", repoId, null, null)), + new ActionListener<>() { + @Override + public void onResponse(Map>> snapshotsToBeDeleted) { + logger.info("--> forcing failure"); + throw new ElasticsearchException("forced failure"); + } - @Override - public void onFailure(Exception e) { - errHandlerCalled.set(e); + @Override + public void onFailure(Exception e) { + errHandlerCalled.set(e); + } } - }); + ); assertNotNull(errHandlerCalled.get()); assertThat(errHandlerCalled.get().getMessage(), equalTo("forced failure")); @@ -597,14 +585,14 @@ public ClusterState createState(OperationMode mode, SnapshotLifecyclePolicy... p } private static class MockSnapshotRetentionTask extends SnapshotRetentionTask { - private final Supplier>> snapshotRetriever; + private final Supplier>>> snapshotRetriever; private final DeleteSnapshotMock deleteRunner; MockSnapshotRetentionTask( Client client, ClusterService clusterService, SnapshotHistoryStore historyStore, - Supplier>> snapshotRetriever, + Supplier>>> snapshotRetriever, DeleteSnapshotMock deleteRunner, LongSupplier nanoSupplier ) { @@ -614,10 +602,10 @@ private static class MockSnapshotRetentionTask extends SnapshotRetentionTask { } @Override - void getAllRetainableSnapshots( + void getSnapshotsEligibleForDeletion( Collection repositories, - Set policies, - ActionListener>> listener + Map policies, + ActionListener>>> listener ) { listener.onResponse(this.snapshotRetriever.get()); }