Skip to content

Commit bf8550c

Browse files
jakelandiskcm
authored andcommitted
ingest: processor stats (#34724)
This change introduces stats per processors. Total, time, failed, current are currently supported. All pipelines will now show all top level processors that belong to it. Failure processors are not displayed, however, the time taken to execute the failure chain is part of the stats for the top level processor. The processor name is the type of the processor, ordered as defined in the pipeline. If a tag for the processor is found, then the tag is appended to the type. Pipeline processors will have the pipeline name appended to the name of the name of the processors (before the tag if one exists). If more then one pipeline is used to process the document, then each pipeline will carry its own stats. The outer most pipeline will also include the inner most pipeline stats. Conditional processors will only included in the stats if the condition evaluates to true.
1 parent 888ea5c commit bf8550c

13 files changed

+697
-151
lines changed

build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,8 +170,8 @@ task verifyVersions {
170170
* the enabled state of every bwc task. It should be set back to true
171171
* after the backport of the backcompat code is complete.
172172
*/
173-
final boolean bwc_tests_enabled = true
174-
final String bwc_tests_disabled_issue = "" /* place a PR link here when committing bwc changes */
173+
final boolean bwc_tests_enabled = false
174+
final String bwc_tests_disabled_issue = "https://github.com/elastic/elasticsearch/pull/34724" /* place a PR link here when committing bwc changes */
175175
if (bwc_tests_enabled == false) {
176176
if (bwc_tests_disabled_issue.isEmpty()) {
177177
throw new GradleException("bwc_tests_disabled_issue must be set when bwc_tests_enabled == false")

server/src/main/java/org/elasticsearch/ingest/CompoundProcessor.java

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,15 @@
2020
package org.elasticsearch.ingest;
2121

2222
import org.elasticsearch.ElasticsearchException;
23+
import org.elasticsearch.common.collect.Tuple;
2324

2425
import java.util.ArrayList;
2526
import java.util.Arrays;
2627
import java.util.Collections;
2728
import java.util.List;
2829
import java.util.Map;
30+
import java.util.concurrent.TimeUnit;
31+
import java.util.function.LongSupplier;
2932
import java.util.stream.Collectors;
3033

3134
/**
@@ -40,16 +43,33 @@ public class CompoundProcessor implements Processor {
4043
private final boolean ignoreFailure;
4144
private final List<Processor> processors;
4245
private final List<Processor> onFailureProcessors;
46+
private final List<Tuple<Processor, IngestMetric>> processorsWithMetrics;
47+
private final LongSupplier relativeTimeProvider;
48+
49+
CompoundProcessor(LongSupplier relativeTimeProvider, Processor... processor) {
50+
this(false, Arrays.asList(processor), Collections.emptyList(), relativeTimeProvider);
51+
}
4352

4453
public CompoundProcessor(Processor... processor) {
4554
this(false, Arrays.asList(processor), Collections.emptyList());
4655
}
4756

4857
public CompoundProcessor(boolean ignoreFailure, List<Processor> processors, List<Processor> onFailureProcessors) {
58+
this(ignoreFailure, processors, onFailureProcessors, System::nanoTime);
59+
}
60+
CompoundProcessor(boolean ignoreFailure, List<Processor> processors, List<Processor> onFailureProcessors,
61+
LongSupplier relativeTimeProvider) {
4962
super();
5063
this.ignoreFailure = ignoreFailure;
5164
this.processors = processors;
5265
this.onFailureProcessors = onFailureProcessors;
66+
this.relativeTimeProvider = relativeTimeProvider;
67+
this.processorsWithMetrics = new ArrayList<>(processors.size());
68+
processors.forEach(p -> processorsWithMetrics.add(new Tuple<>(p, new IngestMetric())));
69+
}
70+
71+
List<Tuple<Processor, IngestMetric>> getProcessorsWithMetrics() {
72+
return processorsWithMetrics;
5373
}
5474

5575
public boolean isIgnoreFailure() {
@@ -94,12 +114,17 @@ public String getTag() {
94114

95115
@Override
96116
public IngestDocument execute(IngestDocument ingestDocument) throws Exception {
97-
for (Processor processor : processors) {
117+
for (Tuple<Processor, IngestMetric> processorWithMetric : processorsWithMetrics) {
118+
Processor processor = processorWithMetric.v1();
119+
IngestMetric metric = processorWithMetric.v2();
120+
long startTimeInNanos = relativeTimeProvider.getAsLong();
98121
try {
122+
metric.preIngest();
99123
if (processor.execute(ingestDocument) == null) {
100124
return null;
101125
}
102126
} catch (Exception e) {
127+
metric.ingestFailed();
103128
if (ignoreFailure) {
104129
continue;
105130
}
@@ -112,11 +137,15 @@ public IngestDocument execute(IngestDocument ingestDocument) throws Exception {
112137
executeOnFailure(ingestDocument, compoundProcessorException);
113138
break;
114139
}
140+
} finally {
141+
long ingestTimeInMillis = TimeUnit.NANOSECONDS.toMillis(relativeTimeProvider.getAsLong() - startTimeInNanos);
142+
metric.postIngest(ingestTimeInMillis);
115143
}
116144
}
117145
return ingestDocument;
118146
}
119147

148+
120149
void executeOnFailure(IngestDocument ingestDocument, ElasticsearchException exception) throws Exception {
121150
try {
122151
putFailureMetadata(ingestDocument, exception);

server/src/main/java/org/elasticsearch/ingest/ConditionalProcessor.java

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@
2828
import java.util.ListIterator;
2929
import java.util.Map;
3030
import java.util.Set;
31+
import java.util.concurrent.TimeUnit;
32+
import java.util.function.LongSupplier;
3133
import java.util.stream.Collectors;
3234
import org.elasticsearch.script.IngestConditionalScript;
3335
import org.elasticsearch.script.Script;
@@ -42,24 +44,51 @@ public class ConditionalProcessor extends AbstractProcessor {
4244
private final ScriptService scriptService;
4345

4446
private final Processor processor;
47+
private final IngestMetric metric;
48+
private final LongSupplier relativeTimeProvider;
4549

4650
ConditionalProcessor(String tag, Script script, ScriptService scriptService, Processor processor) {
51+
this(tag, script, scriptService, processor, System::nanoTime);
52+
}
53+
54+
ConditionalProcessor(String tag, Script script, ScriptService scriptService, Processor processor, LongSupplier relativeTimeProvider) {
4755
super(tag);
4856
this.condition = script;
4957
this.scriptService = scriptService;
5058
this.processor = processor;
59+
this.metric = new IngestMetric();
60+
this.relativeTimeProvider = relativeTimeProvider;
5161
}
5262

5363
@Override
5464
public IngestDocument execute(IngestDocument ingestDocument) throws Exception {
5565
IngestConditionalScript script =
5666
scriptService.compile(condition, IngestConditionalScript.CONTEXT).newInstance(condition.getParams());
5767
if (script.execute(new UnmodifiableIngestData(ingestDocument.getSourceAndMetadata()))) {
58-
return processor.execute(ingestDocument);
68+
// Only record metric if the script evaluates to true
69+
long startTimeInNanos = relativeTimeProvider.getAsLong();
70+
try {
71+
metric.preIngest();
72+
return processor.execute(ingestDocument);
73+
} catch (Exception e) {
74+
metric.ingestFailed();
75+
throw e;
76+
} finally {
77+
long ingestTimeInMillis = TimeUnit.NANOSECONDS.toMillis(relativeTimeProvider.getAsLong() - startTimeInNanos);
78+
metric.postIngest(ingestTimeInMillis);
79+
}
5980
}
6081
return ingestDocument;
6182
}
6283

84+
Processor getProcessor() {
85+
return processor;
86+
}
87+
88+
IngestMetric getMetric() {
89+
return metric;
90+
}
91+
6392
@Override
6493
public String getType() {
6594
return TYPE;

server/src/main/java/org/elasticsearch/ingest/IngestService.java

Lines changed: 96 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,6 @@
1919

2020
package org.elasticsearch.ingest;
2121

22-
import java.util.ArrayList;
23-
import java.util.Collections;
24-
import java.util.HashMap;
25-
import java.util.HashSet;
26-
import java.util.List;
27-
import java.util.Map;
28-
import java.util.Objects;
29-
import java.util.Set;
30-
import java.util.concurrent.TimeUnit;
31-
import java.util.function.BiConsumer;
32-
import java.util.function.Consumer;
33-
import java.util.stream.Collectors;
34-
3522
import org.elasticsearch.ElasticsearchParseException;
3623
import org.elasticsearch.ExceptionsHelper;
3724
import org.elasticsearch.ResourceNotFoundException;
@@ -49,6 +36,7 @@
4936
import org.elasticsearch.cluster.metadata.MetaData;
5037
import org.elasticsearch.cluster.node.DiscoveryNode;
5138
import org.elasticsearch.cluster.service.ClusterService;
39+
import org.elasticsearch.common.collect.Tuple;
5240
import org.elasticsearch.common.regex.Regex;
5341
import org.elasticsearch.common.unit.TimeValue;
5442
import org.elasticsearch.common.util.concurrent.AbstractRunnable;
@@ -61,6 +49,19 @@
6149
import org.elasticsearch.script.ScriptService;
6250
import org.elasticsearch.threadpool.ThreadPool;
6351

52+
import java.util.ArrayList;
53+
import java.util.Collections;
54+
import java.util.HashMap;
55+
import java.util.HashSet;
56+
import java.util.Iterator;
57+
import java.util.List;
58+
import java.util.Map;
59+
import java.util.Objects;
60+
import java.util.Set;
61+
import java.util.concurrent.TimeUnit;
62+
import java.util.function.BiConsumer;
63+
import java.util.function.Consumer;
64+
6465
/**
6566
* Holder class for several ingest related services.
6667
*/
@@ -262,11 +263,59 @@ public void applyClusterState(final ClusterChangedEvent event) {
262263
Pipeline originalPipeline = originalPipelines.get(id);
263264
if (originalPipeline != null) {
264265
pipeline.getMetrics().add(originalPipeline.getMetrics());
266+
List<Tuple<Processor, IngestMetric>> oldPerProcessMetrics = new ArrayList<>();
267+
List<Tuple<Processor, IngestMetric>> newPerProcessMetrics = new ArrayList<>();
268+
getProcessorMetrics(originalPipeline.getCompoundProcessor(), oldPerProcessMetrics);
269+
getProcessorMetrics(pipeline.getCompoundProcessor(), newPerProcessMetrics);
270+
//Best attempt to populate new processor metrics using a parallel array of the old metrics. This is not ideal since
271+
//the per processor metrics may get reset when the arrays don't match. However, to get to an ideal model, unique and
272+
//consistent id's per processor and/or semantic equals for each processor will be needed.
273+
if (newPerProcessMetrics.size() == oldPerProcessMetrics.size()) {
274+
Iterator<Tuple<Processor, IngestMetric>> oldMetricsIterator = oldPerProcessMetrics.iterator();
275+
for (Tuple<Processor, IngestMetric> compositeMetric : newPerProcessMetrics) {
276+
String type = compositeMetric.v1().getType();
277+
IngestMetric metric = compositeMetric.v2();
278+
if (oldMetricsIterator.hasNext()) {
279+
Tuple<Processor, IngestMetric> oldCompositeMetric = oldMetricsIterator.next();
280+
String oldType = oldCompositeMetric.v1().getType();
281+
IngestMetric oldMetric = oldCompositeMetric.v2();
282+
if (type.equals(oldType)) {
283+
metric.add(oldMetric);
284+
}
285+
}
286+
}
287+
}
265288
}
266289
});
267290
}
268291
}
269292

293+
/**
294+
* Recursive method to obtain all of the non-failure processors for given compoundProcessor. Since conditionals are implemented as
295+
* wrappers to the actual processor, always prefer the actual processor's metric over the conditional processor's metric.
296+
* @param compoundProcessor The compound processor to start walking the non-failure processors
297+
* @param processorMetrics The list of {@link Processor} {@link IngestMetric} tuples.
298+
* @return the processorMetrics for all non-failure processor that belong to the original compoundProcessor
299+
*/
300+
private static List<Tuple<Processor, IngestMetric>> getProcessorMetrics(CompoundProcessor compoundProcessor,
301+
List<Tuple<Processor, IngestMetric>> processorMetrics) {
302+
//only surface the top level non-failure processors, on-failure processor times will be included in the top level non-failure
303+
for (Tuple<Processor, IngestMetric> processorWithMetric : compoundProcessor.getProcessorsWithMetrics()) {
304+
Processor processor = processorWithMetric.v1();
305+
IngestMetric metric = processorWithMetric.v2();
306+
if (processor instanceof CompoundProcessor) {
307+
getProcessorMetrics((CompoundProcessor) processor, processorMetrics);
308+
} else {
309+
//Prefer the conditional's metric since it only includes metrics when the conditional evaluated to true.
310+
if (processor instanceof ConditionalProcessor) {
311+
metric = ((ConditionalProcessor) processor).getMetric();
312+
}
313+
processorMetrics.add(new Tuple<>(processor, metric));
314+
}
315+
}
316+
return processorMetrics;
317+
}
318+
270319
private static Pipeline substitutePipeline(String id, ElasticsearchParseException e) {
271320
String tag = e.getHeaderKeys().contains("processor_tag") ? e.getHeader("processor_tag").get(0) : null;
272321
String type = e.getHeaderKeys().contains("processor_type") ? e.getHeader("processor_type").get(0) : "unknown";
@@ -371,11 +420,42 @@ protected void doRun() {
371420
}
372421

373422
public IngestStats stats() {
423+
IngestStats.Builder statsBuilder = new IngestStats.Builder();
424+
statsBuilder.addTotalMetrics(totalMetrics);
425+
pipelines.forEach((id, pipeline) -> {
426+
CompoundProcessor rootProcessor = pipeline.getCompoundProcessor();
427+
statsBuilder.addPipelineMetrics(id, pipeline.getMetrics());
428+
List<Tuple<Processor, IngestMetric>> processorMetrics = new ArrayList<>();
429+
getProcessorMetrics(rootProcessor, processorMetrics);
430+
processorMetrics.forEach(t -> {
431+
Processor processor = t.v1();
432+
IngestMetric processorMetric = t.v2();
433+
statsBuilder.addProcessorMetrics(id, getProcessorName(processor), processorMetric);
434+
});
435+
});
436+
return statsBuilder.build();
437+
}
374438

375-
Map<String, IngestStats.Stats> statsPerPipeline =
376-
pipelines.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, v -> v.getValue().getMetrics().createStats()));
439+
//package private for testing
440+
static String getProcessorName(Processor processor){
441+
// conditionals are implemented as wrappers around the real processor, so get the real processor for the correct type for the name
442+
if(processor instanceof ConditionalProcessor){
443+
processor = ((ConditionalProcessor) processor).getProcessor();
444+
}
445+
StringBuilder sb = new StringBuilder(5);
446+
sb.append(processor.getType());
377447

378-
return new IngestStats(totalMetrics.createStats(), statsPerPipeline);
448+
if(processor instanceof PipelineProcessor){
449+
String pipelineName = ((PipelineProcessor) processor).getPipelineName();
450+
sb.append(":");
451+
sb.append(pipelineName);
452+
}
453+
String tag = processor.getTag();
454+
if(tag != null && !tag.isEmpty()){
455+
sb.append(":");
456+
sb.append(tag);
457+
}
458+
return sb.toString();
379459
}
380460

381461
private void innerExecute(IndexRequest indexRequest, Pipeline pipeline, Consumer<IndexRequest> itemDroppedHandler) throws Exception {

0 commit comments

Comments
 (0)