Skip to content

Commit fd7efa9

Browse files
Merge branch 'main' into pkar/resolve-index-force-reconn
2 parents 2395fc4 + 256a390 commit fd7efa9

File tree

913 files changed

+34615
-11211
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

913 files changed

+34615
-11211
lines changed

.coderabbit.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ reviews:
88
labels:
99
- "Team:Delivery"
1010
- "Team:Search - Inference"
11+
- "Team:Core/Infra"

AGENTS.md

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -99,19 +99,25 @@ If you encounter any of the following methods, you must go and read their javado
9999
## Backwards compatibility
100100
- For changes to a `Writeable` implementation (`writeTo` and constructor from `StreamInput`), add a new `public static final <UNIQUE_DESCRIPTIVE_NAME> = TransportVersion.fromName("<unique_descriptive_name>")` and use it in the new code paths. Confirm the backport branches and then generate a new version file with `./gradlew generateTransportVersion`.
101101

102-
### CI failure triage with Buildkite and Gradle Enterprise build scans
103-
- Prefer Gradle Enterprise build scans (`https://gradle-enterprise.elastic.co/s/<id>`) over raw logs for root-cause analysis when available.
104-
- If given a Buildkite link, use the Buildkite MCP server first.
105-
- First call `buildkite-list_annotations` and inspect `context=gradle-build-scans-failed` (failed jobs only). If needed, inspect `context=gradle-build-scans` (all jobs).
106-
- If annotations are incomplete, call `buildkite-get_build` and map failed job IDs to `meta_data` keys: `build-scan-<job_id>` and `build-scan-id-<job_id>`.
107-
- Buildkite UI fallback (when MCP is unavailable): Build page -> `Jobs` -> `Failures`, then open/copy the Gradle Enterprise build scan links shown per failed job.
108-
- If given a Gradle Enterprise build scan link directly, start from that link instead of searching Buildkite logs first.
109-
- If `dvcli` is available, use it to extract failed tasks, exact failed tests, primary assertion/error, and reproduction details.
110-
- If `dvcli` is unavailable, do not block: continue with Buildkite MCP logs (`buildkite-search_logs`, `buildkite-tail_logs`, `buildkite-read_logs`), artifacts, and annotations.
111-
- If either tool is missing, suggest installation to the user for faster future triage:
112-
- `dvcli` / `develocity-cli-client`: `https://github.com/breskeby/develocity-cli-client`
113-
- Buildkite MCP setup for AI tools: `https://buildkite.com/docs/apis/mcp-server/remote/configuring-ai-tools`
114-
- For Buildkite URLs that include `#<job_id>`, prioritize that specific job and resolve its corresponding `build-scan-<job_id>` entry.
115-
- In reports, list exact failed tests first, then failed tasks and related build scan URLs.
102+
## CI failure triage with Buildkite and Gradle Enterprise build scans
103+
104+
Prefer Gradle Enterprise build scans (`https://gradle-enterprise.elastic.co/s/<id>`) over raw logs for root-cause analysis when available.
105+
106+
**Primary tool: `dvcli`.** Use it for root-cause analysis on Gradle Enterprise build scans (`https://gradle-enterprise.elastic.co/s/<id>`) whenever possible.
107+
It extracts failed tasks, exact failed tests, primary assertion/error, and reproduction details without requiring the agent to authenticate.
108+
109+
1. If given a Gradle Enterprise build scan link directly, start from that link instead of searching Buildkite logs first.
110+
2. If given a Buildkite link, use the Buildkite MCP server to retrieve Gradle build scans.
111+
- For Buildkite URLs that include `#<job_id>`, prioritize that specific job and resolve its corresponding `build-scan-<job_id>` entry.
112+
- Otherwise call `buildkite-list_annotations` and inspect `context=gradle-build-scans-failed` (failed jobs only). If needed, inspect `context=gradle-build-scans` (all jobs).
113+
- If annotations are incomplete, call `buildkite-get_build` and map failed job IDs to `meta_data` keys: `build-scan-<job_id>` and `build-scan-id-<job_id>`.
114+
- Buildkite UI fallback (when MCP is unavailable): Build page -> `Jobs` -> `Failures`, then open/copy the Gradle Enterprise build scan links shown per failed job.
115+
3. Run `dvcli` against the resolved build scan link to extract failure details.
116+
- If `dvcli` is unavailable, fall back to Buildkite MCP logs (`buildkite-search_logs`, `buildkite-tail_logs`, `buildkite-read_logs`), artifacts, and annotations.
117+
- If either tool is missing, suggest installation to the user for faster future triage:
118+
- `dvcli` / `develocity-cli-client`: `https://github.com/breskeby/develocity-cli-client`
119+
- Buildkite MCP setup for AI tools: `https://buildkite.com/docs/apis/mcp-server/remote/configuring-ai-tools`
120+
121+
In reports, list exact failed tests first, then failed tasks and related build scan URLs.
116122

117123
Stay aligned with `CONTRIBUTING.md`, `BUILDING.md`, and `TESTING.asciidoc`; this AGENTS guide summarizes—but does not replace—those authoritative docs.

benchmarks/build.gradle

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ dependencies {
5151
api(project(':x-pack:plugin:analytics'))
5252
api(project(':x-pack:plugin:logsdb'))
5353
implementation project(path: ':libs:native')
54+
implementation(testFixtures(project(':libs:native')))
5455
implementation project(path: ':libs:simdvec')
5556
implementation (testFixtures(project(path: ':libs:simdvec')))
5657
implementation project(path: ':libs:swisshash')
@@ -103,7 +104,7 @@ tasks.named("run").configure {
103104

104105
tasks.named('test').configure {
105106
if (buildParams.getRuntimeJavaVersion().map{ it.majorVersion.toInteger() }.get() >= 21) {
106-
jvmArgs '--add-modules=jdk.incubator.vector'
107+
jvmArgs '--add-modules=jdk.incubator.vector', '--add-opens=java.base/java.nio=ALL-UNNAMED'
107108
}
108109
}
109110

Lines changed: 281 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,281 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
package org.elasticsearch.benchmark._nightly.esql;
11+
12+
import org.apache.lucene.util.BytesRef;
13+
import org.elasticsearch.benchmark.Utils;
14+
import org.elasticsearch.common.breaker.NoopCircuitBreaker;
15+
import org.elasticsearch.common.util.BigArrays;
16+
import org.elasticsearch.compute.data.Block;
17+
import org.elasticsearch.compute.data.BlockFactory;
18+
import org.elasticsearch.compute.data.BooleanBlock;
19+
import org.elasticsearch.compute.data.BytesRefBlock;
20+
import org.elasticsearch.compute.data.ElementType;
21+
import org.elasticsearch.compute.data.Page;
22+
import org.elasticsearch.compute.operator.BreakingBytesRefBuilder;
23+
import org.elasticsearch.compute.operator.GroupKeyEncoder;
24+
import org.elasticsearch.compute.operator.Operator;
25+
import org.elasticsearch.compute.operator.topn.GroupedTopNOperator;
26+
import org.elasticsearch.compute.operator.topn.TopNEncoder;
27+
import org.elasticsearch.compute.operator.topn.TopNOperator;
28+
import org.elasticsearch.core.Releasables;
29+
import org.openjdk.jmh.annotations.Benchmark;
30+
import org.openjdk.jmh.annotations.BenchmarkMode;
31+
import org.openjdk.jmh.annotations.Fork;
32+
import org.openjdk.jmh.annotations.Measurement;
33+
import org.openjdk.jmh.annotations.Mode;
34+
import org.openjdk.jmh.annotations.OperationsPerInvocation;
35+
import org.openjdk.jmh.annotations.OutputTimeUnit;
36+
import org.openjdk.jmh.annotations.Param;
37+
import org.openjdk.jmh.annotations.Scope;
38+
import org.openjdk.jmh.annotations.State;
39+
import org.openjdk.jmh.annotations.Warmup;
40+
41+
import java.util.ArrayList;
42+
import java.util.Arrays;
43+
import java.util.List;
44+
import java.util.Random;
45+
import java.util.concurrent.TimeUnit;
46+
import java.util.stream.IntStream;
47+
48+
@Warmup(iterations = 5)
49+
@Measurement(iterations = 7)
50+
@BenchmarkMode(Mode.AverageTime)
51+
@OutputTimeUnit(TimeUnit.NANOSECONDS)
52+
@State(Scope.Thread)
53+
@Fork(1)
54+
public class GroupedTopNBenchmark {
55+
56+
private static final BlockFactory blockFactory = BlockFactory.builder(BigArrays.NON_RECYCLING_INSTANCE)
57+
.breaker(new NoopCircuitBreaker("none"))
58+
.build();
59+
60+
private static final int BLOCK_LENGTH = 4 * 1024;
61+
private static final int NUM_PAGES = 1024;
62+
private static final int SELF_TEST_PAGES = 16;
63+
64+
private static final String LONGS = "longs";
65+
private static final String INTS = "ints";
66+
private static final String DOUBLES = "doubles";
67+
private static final String BOOLEANS = "booleans";
68+
private static final String BYTES_REFS = "bytes_refs";
69+
70+
private static final String ASC = "_asc";
71+
private static final String DESC = "_desc";
72+
73+
private static final String AND = "_and_";
74+
75+
static {
76+
Utils.configureBenchmarkLogging();
77+
// Smoke test all the expected values and force loading subclasses more like prod
78+
selfTest();
79+
}
80+
81+
static void selfTest() {
82+
try {
83+
for (String data : GroupedTopNBenchmark.class.getField("data").getAnnotationsByType(Param.class)[0].value()) {
84+
for (String topCount : GroupedTopNBenchmark.class.getField("topCount").getAnnotationsByType(Param.class)[0].value()) {
85+
for (String groupCount : GroupedTopNBenchmark.class.getField("groupCount").getAnnotationsByType(Param.class)[0]
86+
.value()) {
87+
for (String gk : GroupedTopNBenchmark.class.getField("groupKeys").getAnnotationsByType(Param.class)[0].value()) {
88+
run(data, Integer.parseInt(topCount), Integer.parseInt(groupCount), gk, SELF_TEST_PAGES);
89+
}
90+
}
91+
}
92+
}
93+
} catch (NoSuchFieldException e) {
94+
throw new AssertionError();
95+
}
96+
}
97+
98+
@Param({ LONGS + ASC, LONGS + DESC, BYTES_REFS + ASC, LONGS + ASC + AND + LONGS + ASC, LONGS + ASC + AND + BYTES_REFS + ASC })
99+
public String data;
100+
101+
@Param({ "1", "10", "1000" })
102+
public int topCount;
103+
104+
@Param({ "10", "100", "1000" })
105+
public int groupCount;
106+
107+
@Param({ LONGS, BYTES_REFS, LONGS + AND + LONGS, BYTES_REFS + AND + BYTES_REFS, LONGS + AND + BYTES_REFS })
108+
public String groupKeys;
109+
110+
private static Operator operator(String data, int topCount, String groupKeys) {
111+
String[] dataSpec = data.split(AND);
112+
List<ElementType> elementTypes = new ArrayList<>(Arrays.stream(dataSpec).map(GroupedTopNBenchmark::elementType).toList());
113+
List<TopNEncoder> encoders = new ArrayList<>(Arrays.stream(dataSpec).map(GroupedTopNBenchmark::encoder).toList());
114+
List<TopNOperator.SortOrder> sortOrders = IntStream.range(0, dataSpec.length).mapToObj(c -> sortOrder(c, dataSpec[c])).toList();
115+
116+
String[] groupKeySpec = groupKeys.split(AND);
117+
int[] groupKeyChannels = new int[groupKeySpec.length];
118+
for (int i = 0; i < groupKeySpec.length; i++) {
119+
groupKeyChannels[i] = elementTypes.size();
120+
elementTypes.add(elementType(groupKeySpec[i]));
121+
encoders.add(TopNEncoder.DEFAULT_UNSORTABLE);
122+
}
123+
124+
return new GroupedTopNOperator(
125+
blockFactory,
126+
blockFactory.breaker(),
127+
topCount,
128+
elementTypes,
129+
encoders,
130+
sortOrders,
131+
new GroupKeyEncoder(groupKeyChannels, elementTypes, new BreakingBytesRefBuilder(blockFactory.breaker(), "group-key-encoder")),
132+
8 * 1024,
133+
Long.MAX_VALUE
134+
);
135+
}
136+
137+
private static ElementType elementType(String data) {
138+
return switch (data.replace(ASC, "").replace(DESC, "")) {
139+
case LONGS -> ElementType.LONG;
140+
case INTS -> ElementType.INT;
141+
case DOUBLES -> ElementType.DOUBLE;
142+
case BOOLEANS -> ElementType.BOOLEAN;
143+
case BYTES_REFS -> ElementType.BYTES_REF;
144+
default -> throw new IllegalArgumentException("unsupported data type [" + data + "]");
145+
};
146+
}
147+
148+
private static TopNEncoder encoder(String data) {
149+
return switch (data.replace(ASC, "").replace(DESC, "")) {
150+
case LONGS, INTS, DOUBLES, BOOLEANS -> TopNEncoder.DEFAULT_SORTABLE;
151+
case BYTES_REFS -> TopNEncoder.UTF8;
152+
default -> throw new IllegalArgumentException("unsupported data type [" + data + "]");
153+
};
154+
}
155+
156+
private static boolean ascDesc(String data) {
157+
if (data.endsWith(ASC)) {
158+
return true;
159+
} else if (data.endsWith(DESC)) {
160+
return false;
161+
} else {
162+
throw new IllegalArgumentException("data neither asc nor desc: " + data);
163+
}
164+
}
165+
166+
private static TopNOperator.SortOrder sortOrder(int channel, String data) {
167+
return new TopNOperator.SortOrder(channel, ascDesc(data), false);
168+
}
169+
170+
private static void checkExpected(int topCount, int groupCount, int numPages, List<Page> pages) {
171+
int effectiveGroupCount = Math.min(groupCount, BLOCK_LENGTH);
172+
long expectedOutput = 0;
173+
for (int g = 0; g < effectiveGroupCount; g++) {
174+
int rowsPerPage = BLOCK_LENGTH / effectiveGroupCount + (g < BLOCK_LENGTH % effectiveGroupCount ? 1 : 0);
175+
long totalRowsForGroup = (long) rowsPerPage * numPages;
176+
expectedOutput += Math.min(topCount, totalRowsForGroup);
177+
}
178+
long actualOutput = pages.stream().mapToLong(Page::getPositionCount).sum();
179+
if (expectedOutput != actualOutput) {
180+
throw new AssertionError("expected [" + expectedOutput + "] but got [" + actualOutput + "]");
181+
}
182+
}
183+
184+
private static Page page(String data, int groupCount, String groupKeys) {
185+
String[] dataSpec = data.split(AND);
186+
String[] groupKeySpec = groupKeys.split(AND);
187+
int effectiveGroupCount = Math.min(groupCount, BLOCK_LENGTH);
188+
int divisor = (int) Math.ceil(Math.sqrt(effectiveGroupCount));
189+
190+
Block[] blocks = new Block[dataSpec.length + groupKeySpec.length];
191+
for (int i = 0; i < dataSpec.length; i++) {
192+
blocks[i] = block(dataSpec[i]);
193+
}
194+
for (int k = 0; k < groupKeySpec.length; k++) {
195+
blocks[dataSpec.length + k] = groupKeyBlock(groupKeySpec[k], effectiveGroupCount, divisor, k, groupKeySpec.length);
196+
}
197+
return new Page(blocks);
198+
}
199+
200+
private static Block block(String data) {
201+
return switch (data.replace(ASC, "").replace(DESC, "")) {
202+
case LONGS -> {
203+
var builder = blockFactory.newLongBlockBuilder(BLOCK_LENGTH);
204+
new Random().longs(BLOCK_LENGTH, 0, Long.MAX_VALUE).forEach(builder::appendLong);
205+
yield builder.build();
206+
}
207+
case INTS -> {
208+
var builder = blockFactory.newIntBlockBuilder(BLOCK_LENGTH);
209+
new Random().ints(BLOCK_LENGTH, 0, Integer.MAX_VALUE).forEach(builder::appendInt);
210+
yield builder.build();
211+
}
212+
case DOUBLES -> {
213+
var builder = blockFactory.newDoubleBlockBuilder(BLOCK_LENGTH);
214+
new Random().doubles(BLOCK_LENGTH, 0, Double.MAX_VALUE).forEach(builder::appendDouble);
215+
yield builder.build();
216+
}
217+
case BOOLEANS -> {
218+
BooleanBlock.Builder builder = blockFactory.newBooleanBlockBuilder(BLOCK_LENGTH);
219+
new Random().ints(BLOCK_LENGTH, 0, 2).forEach(i -> builder.appendBoolean(i == 1));
220+
yield builder.build();
221+
}
222+
case BYTES_REFS -> {
223+
BytesRefBlock.Builder builder = blockFactory.newBytesRefBlockBuilder(BLOCK_LENGTH);
224+
new Random().ints(BLOCK_LENGTH, 0, Integer.MAX_VALUE)
225+
.forEach(i -> builder.appendBytesRef(new BytesRef(Integer.toString(i))));
226+
yield builder.build();
227+
}
228+
default -> throw new UnsupportedOperationException("unsupported data [" + data + "]");
229+
};
230+
}
231+
232+
private static Block groupKeyBlock(String groupKeyType, int effectiveGroupCount, int divisor, int keyIndex, int groupKeyCount) {
233+
return switch (groupKeyType) {
234+
case LONGS -> {
235+
var builder = blockFactory.newLongBlockBuilder(BLOCK_LENGTH);
236+
for (int i = 0; i < BLOCK_LENGTH; i++) {
237+
int groupId = i % effectiveGroupCount;
238+
long keyValue = groupKeyCount == 1 ? groupId : (keyIndex == 0 ? groupId / divisor : groupId % divisor);
239+
builder.appendLong(keyValue);
240+
}
241+
yield builder.build();
242+
}
243+
case BYTES_REFS -> {
244+
BytesRefBlock.Builder builder = blockFactory.newBytesRefBlockBuilder(BLOCK_LENGTH);
245+
for (int i = 0; i < BLOCK_LENGTH; i++) {
246+
int groupId = i % effectiveGroupCount;
247+
long keyValue = groupKeyCount == 1 ? groupId : (keyIndex == 0 ? groupId / divisor : groupId % divisor);
248+
builder.appendBytesRef(new BytesRef(Long.toString(keyValue)));
249+
}
250+
yield builder.build();
251+
}
252+
default -> throw new IllegalArgumentException("unsupported group key type [" + groupKeyType + "]");
253+
};
254+
}
255+
256+
@Benchmark
257+
@OperationsPerInvocation(NUM_PAGES * BLOCK_LENGTH)
258+
public void run() {
259+
run(data, topCount, groupCount, groupKeys, NUM_PAGES);
260+
}
261+
262+
private static void run(String data, int topCount, int groupCount, String groupKeys, int numPages) {
263+
try (Operator operator = operator(data, topCount, groupKeys)) {
264+
Page page = page(data, groupCount, groupKeys);
265+
for (int i = 0; i < numPages; i++) {
266+
operator.addInput(page.shallowCopy());
267+
}
268+
operator.finish();
269+
List<Page> results = new ArrayList<>();
270+
try {
271+
Page p;
272+
while ((p = operator.getOutput()) != null) {
273+
results.add(p);
274+
}
275+
checkExpected(topCount, groupCount, numPages, results);
276+
} finally {
277+
Releasables.close(results);
278+
}
279+
}
280+
}
281+
}

0 commit comments

Comments
 (0)