This is an automated email from the ASF dual-hosted git repository. wusheng pushed a commit to branch groovy-replace in repository https://gitbox.apache.org/repos/asf/skywalking.git
commit 2f928fcf50c8b1a2f0c20ccfa114914a93d71a64 Author: Wu Sheng <[email protected]> AuthorDate: Sat Feb 28 14:22:14 2026 +0800 Add pure Java interfaces and functional overloads for MAL/LAL (Phase 1) Add MalExpression, MalFilter, LalExpression functional interfaces and SampleFamilyFunctions (TagFunction, SampleFilter, ForEachFunction, DecorateFunction, PropertiesExtractor). Add Java functional interface overloads alongside existing Groovy Closure methods in SampleFamily, FilterSpec, ExtractorSpec, and SinkSpec. Change InstanceEntityDescription to use Function instead of Closure. All 129 existing tests pass. Co-Authored-By: Claude Opus 4.6 <[email protected]> --- .../oap/log/analyzer/dsl/LalExpression.java | 30 ++++ .../analyzer/dsl/spec/extractor/ExtractorSpec.java | 88 +++++++++++ .../log/analyzer/dsl/spec/filter/FilterSpec.java | 166 +++++++++++++++++++++ .../oap/log/analyzer/dsl/spec/sink/SinkSpec.java | 22 +++ .../InstanceEntityDescription.java | 4 +- .../oap/meter/analyzer/dsl/MalExpression.java | 30 ++++ .../oap/meter/analyzer/dsl/MalFilter.java | 30 ++++ .../oap/meter/analyzer/dsl/SampleFamily.java | 93 +++++++++++- .../meter/analyzer/dsl/SampleFamilyFunctions.java | 77 ++++++++++ 9 files changed, 536 insertions(+), 4 deletions(-) diff --git a/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/dsl/LalExpression.java b/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/dsl/LalExpression.java new file mode 100644 index 0000000000..f96b02f485 --- /dev/null +++ b/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/dsl/LalExpression.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.skywalking.oap.log.analyzer.dsl; + +import org.apache.skywalking.oap.log.analyzer.dsl.spec.filter.FilterSpec; + +/** + * Pure Java replacement for Groovy-based LAL DelegatingScript. + * Each transpiled LAL expression implements this interface. + */ +@FunctionalInterface +public interface LalExpression { + void execute(FilterSpec filterSpec, Binding binding); +} diff --git a/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/dsl/spec/extractor/ExtractorSpec.java b/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/dsl/spec/extractor/ExtractorSpec.java index ee9e58e72e..2a51d10d4f 100644 --- a/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/dsl/spec/extractor/ExtractorSpec.java +++ b/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/dsl/spec/extractor/ExtractorSpec.java @@ -29,6 +29,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.function.Consumer; import java.util.stream.Collectors; import lombok.experimental.Delegate; import org.apache.commons.lang3.StringUtils; @@ -333,6 +334,93 @@ public class ExtractorSpec extends AbstractSpec { sourceReceiver.receive(entity); } + public void metrics(final Consumer<SampleBuilder> consumer) { + if (BINDING.get().shouldAbort()) { + return; + } + final SampleBuilder builder = new SampleBuilder(); + consumer.accept(builder); + + final Sample sample = builder.build(); + final SampleFamily sampleFamily = SampleFamilyBuilder.newBuilder(sample).build(); + + final Optional<List<SampleFamily>> possibleMetricsContainer = BINDING.get().metricsContainer(); + + if (possibleMetricsContainer.isPresent()) { + possibleMetricsContainer.get().add(sampleFamily); + } else { + metricConverts.forEach(it -> it.toMeter( + ImmutableMap.<String, SampleFamily>builder() + .put(sample.getName(), sampleFamily) + .build() + )); + } + } + + public void slowSql(final Consumer<SlowSqlSpec> consumer) { + if (BINDING.get().shouldAbort()) { + return; + } + LogData.Builder log = BINDING.get().log(); + if (log.getLayer() == null + || log.getService() == null + || log.getTimestamp() < 1) { + LOGGER.warn("SlowSql extracts failed, maybe something is not configured."); + return; + } + DatabaseSlowStatementBuilder builder = new DatabaseSlowStatementBuilder(namingControl); + builder.setLayer(Layer.nameOf(log.getLayer())); + + builder.setServiceName(log.getService()); + + BINDING.get().databaseSlowStatement(builder); + + consumer.accept(slowSql); + + if (builder.getId() == null + || builder.getLatency() < 1 + || builder.getStatement() == null) { + LOGGER.warn("SlowSql extracts failed, maybe something is not configured."); + return; + } + + long timeBucketForDB = TimeBucket.getTimeBucket(log.getTimestamp(), DownSampling.Second); + builder.setTimeBucket(timeBucketForDB); + builder.setTimestamp(log.getTimestamp()); + + builder.prepare(); + sourceReceiver.receive(builder.toDatabaseSlowStatement()); + + ServiceMeta serviceMeta = new ServiceMeta(); + serviceMeta.setName(builder.getServiceName()); + serviceMeta.setLayer(builder.getLayer()); + long timeBucket = TimeBucket.getTimeBucket(log.getTimestamp(), DownSampling.Minute); + serviceMeta.setTimeBucket(timeBucket); + sourceReceiver.receive(serviceMeta); + } + + public void sampledTrace(final Consumer<SampledTraceSpec> consumer) { + if (BINDING.get().shouldAbort()) { + return; + } + LogData.Builder log = BINDING.get().log(); + SampledTraceBuilder builder = new SampledTraceBuilder(namingControl); + builder.setLayer(log.getLayer()); + builder.setTimestamp(log.getTimestamp()); + builder.setServiceName(log.getService()); + builder.setServiceInstanceName(log.getServiceInstance()); + builder.setTraceId(log.getTraceContext().getTraceId()); + BINDING.get().sampledTrace(builder); + + consumer.accept(sampledTrace); + + builder.validate(); + final Record record = builder.toRecord(); + final ISource entity = builder.toEntity(); + RecordStreamProcessor.getInstance().in(record); + sourceReceiver.receive(entity); + } + public static class SampleBuilder { @Delegate private final Sample.SampleBuilder sampleBuilder = Sample.builder(); diff --git a/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/dsl/spec/filter/FilterSpec.java b/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/dsl/spec/filter/FilterSpec.java index 7fb7557b75..b83e71b8b8 100644 --- a/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/dsl/spec/filter/FilterSpec.java +++ b/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/dsl/spec/filter/FilterSpec.java @@ -28,6 +28,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; import org.apache.skywalking.apm.network.logging.v3.LogData; import org.apache.skywalking.oap.log.analyzer.dsl.Binding; @@ -189,4 +190,169 @@ public class FilterSpec extends AbstractSpec { public void filter(final Closure<?> cl) { cl.call(); } + + public void text(final Consumer<TextParserSpec> consumer) { + if (BINDING.get().shouldAbort()) { + return; + } + consumer.accept(textParser); + } + + public void text() { + if (BINDING.get().shouldAbort()) { + return; + } + } + + public void json(final Consumer<JsonParserSpec> consumer) { + if (BINDING.get().shouldAbort()) { + return; + } + consumer.accept(jsonParser); + + final LogData.Builder logData = BINDING.get().log(); + try { + final Map<String, Object> parsed = jsonParser.create().readValue( + logData.getBody().getJson().getJson(), parsedType + ); + BINDING.get().parsed(parsed); + } catch (final Exception e) { + if (jsonParser.abortOnFailure()) { + BINDING.get().abort(); + } + } + } + + public void json() { + if (BINDING.get().shouldAbort()) { + return; + } + + final LogData.Builder logData = BINDING.get().log(); + try { + final Map<String, Object> parsed = jsonParser.create().readValue( + logData.getBody().getJson().getJson(), parsedType + ); + BINDING.get().parsed(parsed); + } catch (final Exception e) { + if (jsonParser.abortOnFailure()) { + BINDING.get().abort(); + } + } + } + + public void yaml(final Consumer<YamlParserSpec> consumer) { + if (BINDING.get().shouldAbort()) { + return; + } + consumer.accept(yamlParser); + + final LogData.Builder logData = BINDING.get().log(); + try { + final Map<String, Object> parsed = yamlParser.create().load( + logData.getBody().getYaml().getYaml() + ); + BINDING.get().parsed(parsed); + } catch (final Exception e) { + if (yamlParser.abortOnFailure()) { + BINDING.get().abort(); + } + } + } + + public void yaml() { + if (BINDING.get().shouldAbort()) { + return; + } + + final LogData.Builder logData = BINDING.get().log(); + try { + final Map<String, Object> parsed = yamlParser.create().load( + logData.getBody().getYaml().getYaml() + ); + BINDING.get().parsed(parsed); + } catch (final Exception e) { + if (yamlParser.abortOnFailure()) { + BINDING.get().abort(); + } + } + } + + public void extractor(final Consumer<ExtractorSpec> consumer) { + if (BINDING.get().shouldAbort()) { + return; + } + consumer.accept(extractor); + } + + public void sink(final Consumer<SinkSpec> consumer) { + if (BINDING.get().shouldAbort()) { + return; + } + consumer.accept(sink); + + final Binding b = BINDING.get(); + final LogData.Builder logData = b.log(); + final Message extraLog = b.extraLog(); + + if (!b.shouldSave()) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Log is dropped: {}", TextFormat.shortDebugString(logData)); + } + return; + } + + final Optional<AtomicReference<Log>> container = BINDING.get().logContainer(); + if (container.isPresent()) { + sinkListenerFactories.stream() + .map(LogSinkListenerFactory::create) + .filter(it -> it instanceof RecordSinkListener) + .map(it -> it.parse(logData, extraLog)) + .map(it -> (RecordSinkListener) it) + .map(RecordSinkListener::getLog) + .findFirst() + .ifPresent(log -> container.get().set(log)); + } else { + sinkListenerFactories.stream() + .map(LogSinkListenerFactory::create) + .forEach(it -> it.parse(logData, extraLog).build()); + } + } + + public void sink() { + if (BINDING.get().shouldAbort()) { + return; + } + + final Binding b = BINDING.get(); + final LogData.Builder logData = b.log(); + final Message extraLog = b.extraLog(); + + if (!b.shouldSave()) { + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("Log is dropped: {}", TextFormat.shortDebugString(logData)); + } + return; + } + + final Optional<AtomicReference<Log>> container = BINDING.get().logContainer(); + if (container.isPresent()) { + sinkListenerFactories.stream() + .map(LogSinkListenerFactory::create) + .filter(it -> it instanceof RecordSinkListener) + .map(it -> it.parse(logData, extraLog)) + .map(it -> (RecordSinkListener) it) + .map(RecordSinkListener::getLog) + .findFirst() + .ifPresent(log -> container.get().set(log)); + } else { + sinkListenerFactories.stream() + .map(LogSinkListenerFactory::create) + .forEach(it -> it.parse(logData, extraLog).build()); + } + } + + public void abort() { + BINDING.get().abort(); + } } diff --git a/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/dsl/spec/sink/SinkSpec.java b/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/dsl/spec/sink/SinkSpec.java index 82566f9b25..f2ae371a21 100644 --- a/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/dsl/spec/sink/SinkSpec.java +++ b/oap-server/analyzer/log-analyzer/src/main/java/org/apache/skywalking/oap/log/analyzer/dsl/spec/sink/SinkSpec.java @@ -20,6 +20,7 @@ package org.apache.skywalking.oap.log.analyzer.dsl.spec.sink; import groovy.lang.Closure; import groovy.lang.DelegatesTo; +import java.util.function.Consumer; import org.apache.skywalking.oap.log.analyzer.dsl.spec.AbstractSpec; import org.apache.skywalking.oap.log.analyzer.provider.LogAnalyzerModuleConfig; import org.apache.skywalking.oap.server.library.module.ModuleManager; @@ -44,6 +45,13 @@ public class SinkSpec extends AbstractSpec { cl.call(); } + public void sampler(final Consumer<SamplerSpec> consumer) { + if (BINDING.get().shouldAbort()) { + return; + } + consumer.accept(sampler); + } + @SuppressWarnings("unused") public void enforcer(final Closure<?> cl) { if (BINDING.get().shouldAbort()) { @@ -52,6 +60,13 @@ public class SinkSpec extends AbstractSpec { BINDING.get().save(); } + public void enforcer() { + if (BINDING.get().shouldAbort()) { + return; + } + BINDING.get().save(); + } + @SuppressWarnings("unused") public void dropper(final Closure<?> cl) { if (BINDING.get().shouldAbort()) { @@ -59,4 +74,11 @@ public class SinkSpec extends AbstractSpec { } BINDING.get().drop(); } + + public void dropper() { + if (BINDING.get().shouldAbort()) { + return; + } + BINDING.get().drop(); + } } diff --git a/oap-server/analyzer/meter-analyzer/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/EntityDescription/InstanceEntityDescription.java b/oap-server/analyzer/meter-analyzer/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/EntityDescription/InstanceEntityDescription.java index 04c0a2a5da..83f1e5f87f 100644 --- a/oap-server/analyzer/meter-analyzer/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/EntityDescription/InstanceEntityDescription.java +++ b/oap-server/analyzer/meter-analyzer/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/EntityDescription/InstanceEntityDescription.java @@ -20,6 +20,7 @@ package org.apache.skywalking.oap.meter.analyzer.dsl.EntityDescription; import java.util.List; import java.util.Map; +import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; import lombok.Getter; @@ -27,7 +28,6 @@ import lombok.RequiredArgsConstructor; import lombok.ToString; import org.apache.skywalking.oap.server.core.analysis.Layer; import org.apache.skywalking.oap.server.core.analysis.meter.ScopeType; -import groovy.lang.Closure; @Getter @RequiredArgsConstructor @@ -39,7 +39,7 @@ public class InstanceEntityDescription implements EntityDescription { private final Layer layer; private final String serviceDelimiter; private final String instanceDelimiter; - private final Closure<Map<String, String>> propertiesExtractor; + private final Function<Map<String, String>, Map<String, String>> propertiesExtractor; @Override public List<String> getLabelKeys() { diff --git a/oap-server/analyzer/meter-analyzer/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/MalExpression.java b/oap-server/analyzer/meter-analyzer/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/MalExpression.java new file mode 100644 index 0000000000..cfaf1d5bee --- /dev/null +++ b/oap-server/analyzer/meter-analyzer/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/MalExpression.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.skywalking.oap.meter.analyzer.dsl; + +import java.util.Map; + +/** + * Pure Java replacement for Groovy-based MAL DelegatingScript. + * Each transpiled MAL expression implements this interface. + */ +@FunctionalInterface +public interface MalExpression { + SampleFamily run(Map<String, SampleFamily> samples); +} diff --git a/oap-server/analyzer/meter-analyzer/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/MalFilter.java b/oap-server/analyzer/meter-analyzer/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/MalFilter.java new file mode 100644 index 0000000000..9d8eaa259e --- /dev/null +++ b/oap-server/analyzer/meter-analyzer/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/MalFilter.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.skywalking.oap.meter.analyzer.dsl; + +import java.util.Map; + +/** + * Pure Java replacement for Groovy Closure-based MAL filter expressions. + * Each transpiled filter expression implements this interface. + */ +@FunctionalInterface +public interface MalFilter { + boolean test(Map<String, String> tags); +} diff --git a/oap-server/analyzer/meter-analyzer/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/SampleFamily.java b/oap-server/analyzer/meter-analyzer/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/SampleFamily.java index f72fed4cef..5a979d0a38 100644 --- a/oap-server/analyzer/meter-analyzer/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/SampleFamily.java +++ b/oap-server/analyzer/meter-analyzer/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/SampleFamily.java @@ -60,6 +60,11 @@ import com.google.common.collect.Maps; import groovy.lang.Closure; import io.vavr.Function2; import io.vavr.Function3; +import org.apache.skywalking.oap.meter.analyzer.dsl.SampleFamilyFunctions.DecorateFunction; +import org.apache.skywalking.oap.meter.analyzer.dsl.SampleFamilyFunctions.ForEachFunction; +import org.apache.skywalking.oap.meter.analyzer.dsl.SampleFamilyFunctions.PropertiesExtractor; +import org.apache.skywalking.oap.meter.analyzer.dsl.SampleFamilyFunctions.SampleFilter; +import org.apache.skywalking.oap.meter.analyzer.dsl.SampleFamilyFunctions.TagFunction; import lombok.AccessLevel; import lombok.Builder; import lombok.EqualsAndHashCode; @@ -400,6 +405,26 @@ public class SampleFamily { ); } + @SuppressWarnings(value = "unchecked") + public SampleFamily tag(TagFunction fn) { + if (this == EMPTY) { + return EMPTY; + } + return SampleFamily.build( + this.context, + Arrays.stream(samples) + .map(sample -> { + Map<String, String> arg = Maps.newHashMap(sample.labels); + Map<String, String> r = fn.apply(arg); + return sample.toBuilder() + .labels( + ImmutableMap.copyOf( + Optional.ofNullable(r).orElse(arg))) + .build(); + }).toArray(Sample[]::new) + ); + } + public SampleFamily filter(Closure<Boolean> filter) { if (this == EMPTY) { return EMPTY; @@ -413,6 +438,19 @@ public class SampleFamily { return SampleFamily.build(context, filtered); } + public SampleFamily filter(SampleFilter filter) { + if (this == EMPTY) { + return EMPTY; + } + final Sample[] filtered = Arrays.stream(samples) + .filter(it -> filter.test(it.labels)) + .toArray(Sample[]::new); + if (filtered.length == 0) { + return EMPTY; + } + return SampleFamily.build(context, filtered); + } + /* k8s retags*/ public SampleFamily retagByK8sMeta(String newLabelName, K8sRetagType type, @@ -516,12 +554,30 @@ public class SampleFamily { if (this == EMPTY) { return EMPTY; } + return createMeterSamples(new InstanceEntityDescription( + serviceKeys, instanceKeys, layer, serviceDelimiter, instanceDelimiter, + propertiesExtractor == null ? null : propertiesExtractor::call)); + } + + public SampleFamily instance(List<String> serviceKeys, String serviceDelimiter, + List<String> instanceKeys, String instanceDelimiter, + Layer layer, PropertiesExtractor propertiesExtractor) { + Preconditions.checkArgument(serviceKeys.size() > 0); + Preconditions.checkArgument(instanceKeys.size() > 0); + ExpressionParsingContext.get().ifPresent(ctx -> { + ctx.scopeType = ScopeType.SERVICE_INSTANCE; + ctx.scopeLabels.addAll(serviceKeys); + ctx.scopeLabels.addAll(instanceKeys); + }); + if (this == EMPTY) { + return EMPTY; + } return createMeterSamples(new InstanceEntityDescription( serviceKeys, instanceKeys, layer, serviceDelimiter, instanceDelimiter, propertiesExtractor)); } public SampleFamily instance(List<String> serviceKeys, List<String> instanceKeys, Layer layer) { - return instance(serviceKeys, Const.POINT, instanceKeys, Const.POINT, layer, null); + return instance(serviceKeys, Const.POINT, instanceKeys, Const.POINT, layer, (Closure<Map<String, String>>) null); } public SampleFamily endpoint(List<String> serviceKeys, List<String> endpointKeys, String delimiter, Layer layer) { @@ -601,6 +657,19 @@ public class SampleFamily { }).toArray(Sample[]::new)); } + public SampleFamily forEach(List<String> array, ForEachFunction each) { + if (this == EMPTY) { + return EMPTY; + } + return SampleFamily.build(this.context, Arrays.stream(this.samples).map(sample -> { + Map<String, String> labels = Maps.newHashMap(sample.getLabels()); + for (String element : array) { + each.accept(element, labels); + } + return sample.toBuilder().labels(ImmutableMap.copyOf(labels)).build(); + }).toArray(Sample[]::new)); + } + public SampleFamily processRelation(String detectPointKey, List<String> serviceKeys, List<String> instanceKeys, String sourceProcessIdKey, String destProcessIdKey, String componentKey) { Preconditions.checkArgument(serviceKeys.size() > 0); Preconditions.checkArgument(instanceKeys.size() > 0); @@ -717,6 +786,26 @@ public class SampleFamily { return this; } + public SampleFamily decorate(DecorateFunction c) { + ExpressionParsingContext.get().ifPresent(ctx -> { + if (ctx.getScopeType() != ScopeType.SERVICE) { + throw new IllegalStateException("decorate() should be invoked after service()"); + } + if (ctx.isHistogram()) { + throw new IllegalStateException("decorate() not supported for histogram metrics"); + } + }); + if (this == EMPTY) { + return EMPTY; + } + this.context.getMeterSamples().keySet().forEach(meterEntity -> { + if (meterEntity.getScopeType().equals(ScopeType.SERVICE)) { + c.accept(meterEntity); + } + }); + return this; + } + /** * The parsing context holds key results more than sample collection. */ @@ -777,7 +866,7 @@ public class SampleFamily { InstanceEntityDescription instanceEntityDescription = (InstanceEntityDescription) entityDescription; Map<String, String> properties = null; if (instanceEntityDescription.getPropertiesExtractor() != null) { - properties = instanceEntityDescription.getPropertiesExtractor().call(samples.get(0).labels); + properties = instanceEntityDescription.getPropertiesExtractor().apply(samples.get(0).labels); } return MeterEntity.newServiceInstance( InternalOps.dim(samples, instanceEntityDescription.getServiceKeys(), instanceEntityDescription.getServiceDelimiter()), diff --git a/oap-server/analyzer/meter-analyzer/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/SampleFamilyFunctions.java b/oap-server/analyzer/meter-analyzer/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/SampleFamilyFunctions.java new file mode 100644 index 0000000000..1c9747b56a --- /dev/null +++ b/oap-server/analyzer/meter-analyzer/src/main/java/org/apache/skywalking/oap/meter/analyzer/dsl/SampleFamilyFunctions.java @@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.skywalking.oap.meter.analyzer.dsl; + +import java.util.Map; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; +import org.apache.skywalking.oap.server.core.analysis.meter.MeterEntity; + +/** + * Pure Java functional interfaces replacing Groovy Closure parameters in SampleFamily methods. + */ +public final class SampleFamilyFunctions { + + private SampleFamilyFunctions() { + } + + /** + * Replaces {@code Closure<?>} in {@link SampleFamily#tag(groovy.lang.Closure)}. + * Receives a mutable label map and returns the (possibly modified) map. + */ + @FunctionalInterface + public interface TagFunction extends Function<Map<String, String>, Map<String, String>> { + } + + /** + * Replaces {@code Closure<Boolean>} in {@link SampleFamily#filter(groovy.lang.Closure)}. + * Tests whether a sample's labels match the filter criteria. + */ + @FunctionalInterface + public interface SampleFilter extends Predicate<Map<String, String>> { + } + + /** + * Replaces {@code Closure<Void>} in {@link SampleFamily#forEach(java.util.List, groovy.lang.Closure)}. + * Called for each element in the array with the element value and a mutable labels map. + */ + @FunctionalInterface + public interface ForEachFunction { + void accept(String element, Map<String, String> tags); + } + + /** + * Replaces {@code Closure<Void>} in {@link SampleFamily#decorate(groovy.lang.Closure)}. + * Decorates service meter entities. + */ + @FunctionalInterface + public interface DecorateFunction extends Consumer<MeterEntity> { + } + + /** + * Replaces {@code Closure<Map<String, String>>} in + * {@link SampleFamily#instance(java.util.List, String, java.util.List, String, + * org.apache.skywalking.oap.server.core.analysis.Layer, groovy.lang.Closure)}. + * Extracts instance properties from sample labels. + */ + @FunctionalInterface + public interface PropertiesExtractor extends Function<Map<String, String>, Map<String, String>> { + } +}
