From 6475ab3d1d6651ca2fb8095a2b50a3394716d8da Mon Sep 17 00:00:00 2001 From: Gao Hongtao Date: Fri, 29 Nov 2019 12:54:33 +0800 Subject: [PATCH] Add apdex function to OAL (#3855) * Add apdex function to OAL * Add empty line * Setup config watcher * Add identifier type to function parameter * Add config test * Update score algorithm * Replace responseCode with status * Add comments about apdex score algorithm * Add docs * Add e2e test case * Update test case * Fix disptch class generating error * Update value name of apdex metric * Tuning threshold * Fix single tolerated point bug --- docs/en/setup/backend/apdex-threshold.md | 27 ++++ docs/en/setup/backend/dynamic-config.md | 1 + .../skywalking/oal/rt/grammar/OALParser.g4 | 2 +- .../oal/rt/parser/AnalysisResult.java | 8 +- .../skywalking/oal/rt/parser/Argument.java | 36 +++++ .../skywalking/oal/rt/parser/EntryMethod.java | 34 +++-- .../skywalking/oal/rt/parser/OALListener.java | 9 +- .../code-templates/dispatcher/doMetrics.ftl | 2 +- .../src/main/resources/official_analysis.oal | 1 + .../resources/service-apdex-threshold.yml | 22 +++ oap-server/server-core/pom.xml | 5 + .../oap/server/core/CoreModuleProvider.java | 8 ++ .../core/analysis/ApdexThresholdConfig.java | 102 +++++++++++++ .../analysis/ConfigurationDictionary.java | 35 +++++ .../core/analysis/metrics/ApdexMetrics.java | 86 +++++++++++ .../analysis/ApdexThresholdConfigTest.java | 76 ++++++++++ .../analysis/metrics/ApdexMetricsTest.java | 135 ++++++++++++++++++ .../resources/service-apdex-threshold.yml | 22 +++ .../skywalking/e2e/metrics/MetricsQuery.java | 4 +- 19 files changed, 597 insertions(+), 18 deletions(-) create mode 100644 docs/en/setup/backend/apdex-threshold.md create mode 100644 oap-server/oal-rt/src/main/java/org/apache/skywalking/oal/rt/parser/Argument.java create mode 100644 oap-server/server-bootstrap/src/main/resources/service-apdex-threshold.yml create mode 100644 oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/ApdexThresholdConfig.java create mode 100644 oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/ConfigurationDictionary.java create mode 100644 oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/metrics/ApdexMetrics.java create mode 100644 oap-server/server-core/src/test/java/org/apache/skywalking/oap/server/core/analysis/ApdexThresholdConfigTest.java create mode 100644 oap-server/server-core/src/test/java/org/apache/skywalking/oap/server/core/analysis/metrics/ApdexMetricsTest.java create mode 100644 oap-server/server-core/src/test/resources/service-apdex-threshold.yml diff --git a/docs/en/setup/backend/apdex-threshold.md b/docs/en/setup/backend/apdex-threshold.md new file mode 100644 index 0000000000..584e2daa24 --- /dev/null +++ b/docs/en/setup/backend/apdex-threshold.md @@ -0,0 +1,27 @@ +# Apdex threshold + +Apdex is a measure of response time based against a set threshold. It measures the ratio of satisfactory response times +to unsatisfactory response times. The response time is measured from an asset request to completed delivery back to +the requestor. + +A user defines a response time threshold T. All responses handled in T or less time satisfy the user. + +For example, if T is 1.2 seconds and a response completes in 0.5 seconds, then the user is satisfied. All responses +greater than 1.2 seconds dissatisfy the user. Responses greater than 4.8 seconds frustrate the user. + +The apdex threshold T can be configured in `service-apdex-threshold.yml` file or via [Dynamic Configuration](dynamic-config.md). +The `default` item will be apply to a service isn't defined in this configuration as the default threshold. + +## Configuration Format + +The configuration content includes the service' names and their threshold: + +```yml +# default threshold is 500ms +default: 500 +# example: +# the threshold of service "tomcat" is 1s +# tomcat: 1000 +# the threshold of service "springboot1" is 50ms +# springboot1: 50 +``` diff --git a/docs/en/setup/backend/dynamic-config.md b/docs/en/setup/backend/dynamic-config.md index bdf3f1be94..3e206d215d 100755 --- a/docs/en/setup/backend/dynamic-config.md +++ b/docs/en/setup/backend/dynamic-config.md @@ -9,6 +9,7 @@ Right now, SkyWalking supports following dynamic configurations. |receiver-trace.default.slowDBAccessThreshold| Thresholds of slow Database statement, override `receiver-trace/default/slowDBAccessThreshold` of `applciation.yml`. | default:200,mongodb:50| |receiver-trace.default.uninstrumentedGateways| The uninstrumented gateways, override `gateways.yml`. | same as [`gateways.yml`](uninstrumented-gateways.md#configuration-format) | |alarm.default.alarm-settings| The alarm settings, will override `alarm-settings.yml`. | same as [`alarm-settings.yml`](backend-alarm.md) | +|core.default.apdexThreshold| The apdex threshold settings, will override `service-apdex-threshold.yml`. | same as [`service-apdex-threshold.yml`](apdex-threshold.md) | This feature depends on upstream service, so it is **OFF** as default. diff --git a/oap-server/oal-grammar/src/main/antlr4/org/apache/skywalking/oal/rt/grammar/OALParser.g4 b/oap-server/oal-grammar/src/main/antlr4/org/apache/skywalking/oal/rt/grammar/OALParser.g4 index a7103f3185..d574f78e03 100644 --- a/oap-server/oal-grammar/src/main/antlr4/org/apache/skywalking/oal/rt/grammar/OALParser.g4 +++ b/oap-server/oal-grammar/src/main/antlr4/org/apache/skywalking/oal/rt/grammar/OALParser.g4 @@ -83,7 +83,7 @@ funcParamExpression ; literalExpression - : BOOL_LITERAL | NUMBER_LITERAL + : BOOL_LITERAL | NUMBER_LITERAL | IDENTIFIER ; expression diff --git a/oap-server/oal-rt/src/main/java/org/apache/skywalking/oal/rt/parser/AnalysisResult.java b/oap-server/oal-rt/src/main/java/org/apache/skywalking/oal/rt/parser/AnalysisResult.java index a92cfe34f7..89f468803f 100644 --- a/oap-server/oal-rt/src/main/java/org/apache/skywalking/oal/rt/parser/AnalysisResult.java +++ b/oap-server/oal-rt/src/main/java/org/apache/skywalking/oal/rt/parser/AnalysisResult.java @@ -50,7 +50,7 @@ public class AnalysisResult { private List funcConditionExpressions; - private List funcArgs; + private List funcArgs; private int argGetIdx = 0; private List persistentFields; @@ -88,14 +88,14 @@ public class AnalysisResult { filterExpressionsParserResult.add(conditionExpression); } - public void addFuncArg(String value) { + public void addFuncArg(Argument argument) { if (funcArgs == null) { funcArgs = new LinkedList<>(); } - funcArgs.add(value); + funcArgs.add(argument); } - public String getNextFuncArg() { + public Argument getNextFuncArg() { return funcArgs.get(argGetIdx++); } diff --git a/oap-server/oal-rt/src/main/java/org/apache/skywalking/oal/rt/parser/Argument.java b/oap-server/oal-rt/src/main/java/org/apache/skywalking/oal/rt/parser/Argument.java new file mode 100644 index 0000000000..2c62829ac9 --- /dev/null +++ b/oap-server/oal-rt/src/main/java/org/apache/skywalking/oal/rt/parser/Argument.java @@ -0,0 +1,36 @@ +/* + * 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.oal.rt.parser; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +/** + * Function argument. + * + * @author hongtaogao + */ +@Getter +@RequiredArgsConstructor +public class Argument { + + private final int type; + + private final String text; +} diff --git a/oap-server/oal-rt/src/main/java/org/apache/skywalking/oal/rt/parser/EntryMethod.java b/oap-server/oal-rt/src/main/java/org/apache/skywalking/oal/rt/parser/EntryMethod.java index 8f08e24a4b..7660ad9437 100644 --- a/oap-server/oal-rt/src/main/java/org/apache/skywalking/oal/rt/parser/EntryMethod.java +++ b/oap-server/oal-rt/src/main/java/org/apache/skywalking/oal/rt/parser/EntryMethod.java @@ -20,18 +20,39 @@ package org.apache.skywalking.oal.rt.parser; import java.util.*; import lombok.*; +import org.apache.skywalking.oal.rt.util.ClassMethodUtil; @Getter(AccessLevel.PUBLIC) @Setter(AccessLevel.PUBLIC) public class EntryMethod { - private static final int LITERAL_TYPE = 1; - private static final int EXPRESSION_TYPE = 2; + static final int LITERAL_TYPE = 1; + static final int IDENTIFIER_TYPE = 2; + static final int EXPRESSION_TYPE = 3; private String methodName; private List argTypes = new ArrayList<>(); private List argsExpressions = new ArrayList<>(); - public void addArg(Class parameterType, String expression) { + void addArg(Class parameterType, Argument arg) { + if (arg.getType() == LITERAL_TYPE) { + addArg(parameterType, arg.getType(), arg.getText()); + return; + } + addArg(parameterType, arg.getType(), parameterType.equals(boolean.class) + ? "source." + ClassMethodUtil.toIsMethod(arg.getText()) + "()" + : "source." + ClassMethodUtil.toGetMethod(arg.getText()) + "()"); + } + + void addArg(Class parameterType, String expression) { + addArg(parameterType, LITERAL_TYPE, expression); + } + + void addArg(Expression expression) { + argTypes.add(EXPRESSION_TYPE); + argsExpressions.add(expression); + } + + private void addArg(Class parameterType, int type, String expression) { if (parameterType.equals(int.class)) { expression = "(int)(" + expression + ")"; } else if (parameterType.equals(long.class)) { @@ -41,12 +62,7 @@ public class EntryMethod { } else if (parameterType.equals(float.class)) { expression = "(float)(" + expression + ")"; } - argTypes.add(LITERAL_TYPE); - argsExpressions.add(expression); - } - - public void addArg(Expression expression) { - argTypes.add(EXPRESSION_TYPE); + argTypes.add(type); argsExpressions.add(expression); } } diff --git a/oap-server/oal-rt/src/main/java/org/apache/skywalking/oal/rt/parser/OALListener.java b/oap-server/oal-rt/src/main/java/org/apache/skywalking/oal/rt/parser/OALListener.java index a43d723fb7..06112e5687 100644 --- a/oap-server/oal-rt/src/main/java/org/apache/skywalking/oal/rt/parser/OALListener.java +++ b/oap-server/oal-rt/src/main/java/org/apache/skywalking/oal/rt/parser/OALListener.java @@ -20,7 +20,8 @@ package org.apache.skywalking.oal.rt.parser; import java.util.List; import org.antlr.v4.runtime.misc.NotNull; -import org.apache.skywalking.oal.rt.grammar.*; +import org.apache.skywalking.oal.rt.grammar.OALParser; +import org.apache.skywalking.oal.rt.grammar.OALParserBaseListener; import org.apache.skywalking.oap.server.core.source.DefaultScopeDefine; public class OALListener extends OALParserBaseListener { @@ -148,7 +149,11 @@ public class OALListener extends OALParserBaseListener { //////////// @Override public void enterLiteralExpression(OALParser.LiteralExpressionContext ctx) { - current.addFuncArg(ctx.getText()); + if (ctx.IDENTIFIER() == null) { + current.addFuncArg(new Argument(EntryMethod.LITERAL_TYPE, ctx.getText())); + return; + } + current.addFuncArg(new Argument(EntryMethod.IDENTIFIER_TYPE, ctx.getText())); } private String metricsNameFormat(String source) { diff --git a/oap-server/oal-rt/src/main/resources/code-templates/dispatcher/doMetrics.ftl b/oap-server/oal-rt/src/main/resources/code-templates/dispatcher/doMetrics.ftl index 058352e820..5f7091157e 100644 --- a/oap-server/oal-rt/src/main/resources/code-templates/dispatcher/doMetrics.ftl +++ b/oap-server/oal-rt/src/main/resources/code-templates/dispatcher/doMetrics.ftl @@ -15,7 +15,7 @@ private void do${metricsName}(org.apache.skywalking.oap.server.core.source.${sou metrics.${entryMethod.methodName}( <#list entryMethod.argsExpressions as arg> - <#if entryMethod.argTypes[arg_index] == 1> + <#if entryMethod.argTypes[arg_index] < 3> ${arg} <#else> new org.apache.skywalking.oap.server.core.analysis.metrics.expression.${arg.expressionObject}().match(${arg.left}, ${arg.right}) diff --git a/oap-server/server-bootstrap/src/main/resources/official_analysis.oal b/oap-server/server-bootstrap/src/main/resources/official_analysis.oal index 1b9e52370f..f6263d8dd7 100755 --- a/oap-server/server-bootstrap/src/main/resources/official_analysis.oal +++ b/oap-server/server-bootstrap/src/main/resources/official_analysis.oal @@ -33,6 +33,7 @@ service_p95 = from(Service.latency).p95(10); service_p90 = from(Service.latency).p90(10); service_p75 = from(Service.latency).p75(10); service_p50 = from(Service.latency).p50(10); +service_apdex = from(Service.latency).apdex(name, status); // Service relation scope metrics for topology service_relation_client_cpm = from(ServiceRelation.*).filter(detectPoint == DetectPoint.CLIENT).cpm(); diff --git a/oap-server/server-bootstrap/src/main/resources/service-apdex-threshold.yml b/oap-server/server-bootstrap/src/main/resources/service-apdex-threshold.yml new file mode 100644 index 0000000000..44a09019ca --- /dev/null +++ b/oap-server/server-bootstrap/src/main/resources/service-apdex-threshold.yml @@ -0,0 +1,22 @@ +# 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. + +# default threshold is 500ms +default: 500 +# example: +# the threshold of service "tomcat" is 1s +# tomcat: 1000 +# the threshold of service "springboot1" is 50ms +# springboot1: 50 \ No newline at end of file diff --git a/oap-server/server-core/pom.xml b/oap-server/server-core/pom.xml index edb7d2cace..c51c6e298e 100644 --- a/oap-server/server-core/pom.xml +++ b/oap-server/server-core/pom.xml @@ -85,6 +85,11 @@ grpc-testing test + + io.grpc + grpc-testing + test + diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/CoreModuleProvider.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/CoreModuleProvider.java index b2ae971827..51ec77b5d6 100755 --- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/CoreModuleProvider.java +++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/CoreModuleProvider.java @@ -20,7 +20,9 @@ package org.apache.skywalking.oap.server.core; import java.io.IOException; import org.apache.skywalking.oap.server.configuration.api.ConfigurationModule; +import org.apache.skywalking.oap.server.configuration.api.DynamicConfigurationService; import org.apache.skywalking.oap.server.core.analysis.*; +import org.apache.skywalking.oap.server.core.analysis.metrics.ApdexMetrics; import org.apache.skywalking.oap.server.core.analysis.worker.MetricsStreamProcessor; import org.apache.skywalking.oap.server.core.analysis.worker.TopNStreamProcessor; import org.apache.skywalking.oap.server.core.annotation.AnnotationScan; @@ -59,6 +61,7 @@ public class CoreModuleProvider extends ModuleProvider { private final StorageModels storageModels; private final SourceReceiverImpl receiver; private OALEngine oalEngine; + private ApdexThresholdConfig apdexThresholdConfig; public CoreModuleProvider() { super(); @@ -172,9 +175,12 @@ public class CoreModuleProvider extends ModuleProvider { MetricsStreamProcessor.getInstance().setEnableDatabaseSession(moduleConfig.isEnableDatabaseSession()); TopNStreamProcessor.getInstance().setTopNWorkerReportCycle(moduleConfig.getTopNReportPeriod()); + apdexThresholdConfig = new ApdexThresholdConfig(this); + ApdexMetrics.setDICT(apdexThresholdConfig); } @Override public void start() throws ModuleStartException { + grpcServer.addHandler(new RemoteServiceHandler(getManager())); grpcServer.addHandler(new HealthCheckServiceHandler()); remoteClientManager.start(); @@ -193,6 +199,8 @@ public class CoreModuleProvider extends ModuleProvider { this.getManager().find(ClusterModule.NAME).provider().getService(ClusterRegister.class).registerRemote(gRPCServerInstance); } + DynamicConfigurationService dynamicConfigurationService = getManager().find(ConfigurationModule.NAME).provider().getService(DynamicConfigurationService.class); + dynamicConfigurationService.registerConfigChangeWatcher(apdexThresholdConfig); } @Override public void notifyAfterCompleted() throws ModuleStartException { diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/ApdexThresholdConfig.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/ApdexThresholdConfig.java new file mode 100644 index 0000000000..5e90266787 --- /dev/null +++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/ApdexThresholdConfig.java @@ -0,0 +1,102 @@ +/* + * 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.server.core.analysis; + +import java.io.FileNotFoundException; +import java.io.Reader; +import java.io.StringReader; +import java.util.Collections; +import java.util.Map; +import lombok.extern.slf4j.Slf4j; +import org.apache.skywalking.oap.server.configuration.api.ConfigChangeWatcher; +import org.apache.skywalking.oap.server.core.Const; +import org.apache.skywalking.oap.server.core.CoreModule; +import org.apache.skywalking.oap.server.core.CoreModuleProvider; +import org.apache.skywalking.oap.server.library.util.ResourceUtils; +import org.yaml.snakeyaml.Yaml; + +/** + * Apdex threshold configuration dictionary adapter. + * Looking up a service apdex threshold from dynamic config service. + * + * @author hongtaogao + */ +@Slf4j +public class ApdexThresholdConfig extends ConfigChangeWatcher implements ConfigurationDictionary { + + private static final String CONFIG_FILE_NAME = "service-apdex-threshold.yml"; + + private static final int SYSTEM_RESERVED_THRESHOLD = 500; + + private Map dictionary = Collections.emptyMap(); + + private String rawConfig = Const.EMPTY_STRING; + + public ApdexThresholdConfig(final CoreModuleProvider provider) { + super(CoreModule.NAME, provider, "apdexThreshold"); + try { + updateConfig(ResourceUtils.read(CONFIG_FILE_NAME)); + } catch (final FileNotFoundException e) { + log.error("Cannot config from: {}", CONFIG_FILE_NAME, e); + } + } + + @Override public Number lookup(String name) { + int t = dictionary.getOrDefault(name, -1); + if (t < 0) { + t = dictionary.getOrDefault("default", -1); + } + if (t < 0) { + log.warn("Pick up system reserved threshold {}ms because of config missing", SYSTEM_RESERVED_THRESHOLD); + return SYSTEM_RESERVED_THRESHOLD; + } + if (log.isDebugEnabled()) { + log.debug("Apdex threshold of {} is {}ms", name, t); + } + return t; + } + + @Override public void notify(ConfigChangeEvent value) { + if (EventType.DELETE.equals(value.getEventType())) { + activeSetting(""); + } else { + activeSetting(value.getNewValue()); + } + } + + @Override public String value() { + return rawConfig; + } + + private synchronized void activeSetting(String config) { + if (log.isDebugEnabled()) { + log.debug("Updating using new static config: {}", config); + } + rawConfig = config; + updateConfig(new StringReader(config)); + } + + @SuppressWarnings("unchecked") + private void updateConfig(final Reader contentRender) { + dictionary = (Map)new Yaml().load(contentRender); + if (dictionary == null) { + dictionary = Collections.emptyMap(); + } + } +} diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/ConfigurationDictionary.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/ConfigurationDictionary.java new file mode 100644 index 0000000000..f0b72e0b3f --- /dev/null +++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/ConfigurationDictionary.java @@ -0,0 +1,35 @@ +/* + * 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.server.core.analysis; + +/** + * Dictionary for lookup config item. + * + * @author gaohongtao + */ +public interface ConfigurationDictionary { + + /** + * Lookup a number config item. + * + * @param name config key. + * @return a number config value. + */ + Number lookup(String name); +} diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/metrics/ApdexMetrics.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/metrics/ApdexMetrics.java new file mode 100644 index 0000000000..9ad11173aa --- /dev/null +++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/analysis/metrics/ApdexMetrics.java @@ -0,0 +1,86 @@ +/* + * 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.server.core.analysis.metrics; + +import lombok.Getter; +import lombok.Setter; +import org.apache.skywalking.oap.server.core.analysis.ConfigurationDictionary; +import org.apache.skywalking.oap.server.core.analysis.metrics.annotation.Arg; +import org.apache.skywalking.oap.server.core.analysis.metrics.annotation.Entrance; +import org.apache.skywalking.oap.server.core.analysis.metrics.annotation.MetricsFunction; +import org.apache.skywalking.oap.server.core.analysis.metrics.annotation.SourceFrom; +import org.apache.skywalking.oap.server.core.query.sql.Function; +import org.apache.skywalking.oap.server.core.storage.annotation.Column; + +/** + * Apdex dissatisfaction levels of Tolerating (apdex_t) and Frustrated (apdex_f) indicate how slow site performance + * contributes to poor customer experiences in your app. For example: + * + * 10000: All responses are satisfactory. + * Tolerating responses half satisfy a user. For example, if all responses are Tolerating, then the Apdex value will + * be 5000. + * 0: None of the responses are satisfactory. + * + * @author gaohongtao + */ +@MetricsFunction(functionName = "apdex") +public abstract class ApdexMetrics extends Metrics implements IntValueHolder { + @Setter + private static ConfigurationDictionary DICT; + protected static final String TOTAL_NUM = "total_num"; + // Level: satisfied + protected static final String S_NUM = "s_num"; + // Level: tolerated + protected static final String T_NUM = "t_num"; + protected static final String VALUE = "value"; + + @Getter @Setter @Column(columnName = TOTAL_NUM) private int totalNum; + @Getter @Setter @Column(columnName = S_NUM) private int sNum; + @Getter @Setter @Column(columnName = T_NUM) private int tNum; + @Getter @Setter @Column(columnName = VALUE, isValue = true, function = Function.Avg) private int value; + + @Entrance + public final void combine(@SourceFrom int value, @Arg String name, @Arg boolean status) { + int t = DICT.lookup(name).intValue(); + int t4 = t * 4; + totalNum++; + if (!status || value >= t4) { + return; + } + if (value >= t) { + tNum++; + } else { + sNum++; + } + } + + @Override public final void combine(Metrics metrics) { + tNum += ((ApdexMetrics)metrics).tNum; + sNum += ((ApdexMetrics)metrics).sNum; + totalNum += ((ApdexMetrics)metrics).totalNum; + } + + @Override public void calculate() { + value = (sNum * 10000 + tNum * 10000 / 2) / totalNum; + } + + @Override public int getValue() { + return value; + } +} diff --git a/oap-server/server-core/src/test/java/org/apache/skywalking/oap/server/core/analysis/ApdexThresholdConfigTest.java b/oap-server/server-core/src/test/java/org/apache/skywalking/oap/server/core/analysis/ApdexThresholdConfigTest.java new file mode 100644 index 0000000000..f150acb022 --- /dev/null +++ b/oap-server/server-core/src/test/java/org/apache/skywalking/oap/server/core/analysis/ApdexThresholdConfigTest.java @@ -0,0 +1,76 @@ +/* + * 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.server.core.analysis; + +import java.util.Set; +import org.apache.skywalking.oap.server.configuration.api.ConfigTable; +import org.apache.skywalking.oap.server.configuration.api.ConfigWatcherRegister; +import org.apache.skywalking.oap.server.core.CoreModuleProvider; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.runners.MockitoJUnitRunner; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class ApdexThresholdConfigTest { + + @Mock + private CoreModuleProvider provider; + + @Test + public void testLookupOfBeforeInit() { + ApdexThresholdConfig config = new ApdexThresholdConfig(provider); + assertThat(config.lookup("foo"), is(500)); + assertThat(config.lookup("default"), is(500)); + assertThat(config.lookup("bar"), is(500)); + } + + @Test(timeout = 20000) + public void testLookupOfDynamicUpdate() throws InterruptedException { + ConfigWatcherRegister register = new MockConfigWatcherRegister(3); + when(provider.name()).thenReturn("default"); + ApdexThresholdConfig config = new ApdexThresholdConfig(provider); + register.registerConfigChangeWatcher(config); + register.start(); + + while (config.lookup("foo").intValue() == 500) { + Thread.sleep(2000); + } + assertThat(config.lookup("foo"), is(200)); + assertThat(config.lookup("default"), is(1000)); + assertThat(config.lookup("bar"), is(1000)); + } + + public static class MockConfigWatcherRegister extends ConfigWatcherRegister { + + public MockConfigWatcherRegister(long syncPeriod) { + super(syncPeriod); + } + + @Override public ConfigTable readConfig(Set keys) { + ConfigTable table = new ConfigTable(); + table.add(new ConfigTable.ConfigItem("core.default.apdexThreshold", "default: 1000 \nfoo: 200")); + return table; + } + } +} \ No newline at end of file diff --git a/oap-server/server-core/src/test/java/org/apache/skywalking/oap/server/core/analysis/metrics/ApdexMetricsTest.java b/oap-server/server-core/src/test/java/org/apache/skywalking/oap/server/core/analysis/metrics/ApdexMetricsTest.java new file mode 100644 index 0000000000..4408a13b1c --- /dev/null +++ b/oap-server/server-core/src/test/java/org/apache/skywalking/oap/server/core/analysis/metrics/ApdexMetricsTest.java @@ -0,0 +1,135 @@ +/* + * 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.server.core.analysis.metrics; + +import org.apache.skywalking.oap.server.core.remote.grpc.proto.RemoteData; +import org.junit.Before; +import org.junit.Test; + +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.*; + +public class ApdexMetricsTest { + + @Before + public void setUp() { + ApdexMetrics.setDICT(name -> name.equals("foo") ? 500 : 1000); + } + + @Test + public void testEntrance() { + ApdexMetrics apdex = new ApdexMetricsImpl(); + apdex.combine(200, "foo", true); + apdex.calculate(); + assertThat(apdex.getValue(), is(10000)); + + apdex = new ApdexMetricsImpl(); + apdex.combine(1000, "foo", true); + apdex.calculate(); + assertThat(apdex.getValue(), is(5000)); + + apdex = new ApdexMetricsImpl(); + apdex.combine(2000, "foo", true); + apdex.calculate(); + assertThat(apdex.getValue(), is(0)); + + apdex = new ApdexMetricsImpl(); + apdex.combine(200, "foo", true); + apdex.combine(300, "bar", true); + apdex.calculate(); + assertThat(apdex.getValue(), is(10000)); + + apdex = new ApdexMetricsImpl(); + apdex.combine(200, "foo", true); + apdex.combine(1500, "bar", true); + apdex.calculate(); + assertThat(apdex.getValue(), is(7500)); + + apdex = new ApdexMetricsImpl(); + apdex.combine(200, "foo", true); + apdex.combine(300, "bar", false); + apdex.calculate(); + assertThat(apdex.getValue(), is(5000)); + + apdex = new ApdexMetricsImpl(); + apdex.combine(200, "foo", true); + apdex.combine(1500, "bar", false); + apdex.calculate(); + assertThat(apdex.getValue(), is(5000)); + + apdex = new ApdexMetricsImpl(); + apdex.combine(200, "foo", true); + apdex.combine(5000, "bar", true); + apdex.calculate(); + assertThat(apdex.getValue(), is(5000)); + } + + @Test + public void testCombine() { + ApdexMetrics apdex1 = new ApdexMetricsImpl(); + apdex1.combine(200, "foo", true); + apdex1.combine(300, "bar", true); + apdex1.combine(200, "foo", true); + apdex1.combine(1500, "bar", true); + + + ApdexMetrics apdex2 = new ApdexMetricsImpl(); + apdex2.combine(200, "foo", true); + apdex2.combine(300, "bar", false); + apdex2.combine(200, "foo", true); + apdex2.combine(1500, "bar", false); + apdex2.combine(200, "foo", true); + apdex2.combine(5000, "bar", true); + + apdex1.combine(apdex2); + apdex1.calculate(); + assertThat(apdex1.getValue(), is(6500)); + } + + public class ApdexMetricsImpl extends ApdexMetrics { + + @Override public String id() { + return null; + } + + @Override public Metrics toHour() { + return null; + } + + @Override public Metrics toDay() { + return null; + } + + @Override public Metrics toMonth() { + return null; + } + + @Override public int remoteHashCode() { + return 0; + } + + @Override public void deserialize(RemoteData remoteData) { + + } + + @Override public RemoteData.Builder serialize() { + return null; + } + } +} diff --git a/oap-server/server-core/src/test/resources/service-apdex-threshold.yml b/oap-server/server-core/src/test/resources/service-apdex-threshold.yml new file mode 100644 index 0000000000..44a09019ca --- /dev/null +++ b/oap-server/server-core/src/test/resources/service-apdex-threshold.yml @@ -0,0 +1,22 @@ +# 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. + +# default threshold is 500ms +default: 500 +# example: +# the threshold of service "tomcat" is 1s +# tomcat: 1000 +# the threshold of service "springboot1" is 50ms +# springboot1: 50 \ No newline at end of file diff --git a/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/metrics/MetricsQuery.java b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/metrics/MetricsQuery.java index 5256c8c5de..2cd9555ed2 100644 --- a/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/metrics/MetricsQuery.java +++ b/test/e2e/e2e-base/src/main/java/org/apache/skywalking/e2e/metrics/MetricsQuery.java @@ -29,12 +29,14 @@ public class MetricsQuery extends AbstractQuery { public static String SERVICE_P90 = "service_p90"; public static String SERVICE_P75 = "service_p75"; public static String SERVICE_P50 = "service_p50"; + public static String SERVICE_APDEX = "service_apdex"; public static String[] ALL_SERVICE_METRICS = { SERVICE_P99, SERVICE_P95, SERVICE_P90, SERVICE_P75, - SERVICE_P50 + SERVICE_P50, + SERVICE_APDEX }; public static String ENDPOINT_P99 = "endpoint_p99"; -- GitLab