From cc9ea271a720e8def161a583595f75da9bda77a5 Mon Sep 17 00:00:00 2001 From: mrproliu <741550557@qq.com> Date: Mon, 22 Jun 2020 14:07:34 +0800 Subject: [PATCH] Provide agent-side meter api (#4816) --- .../apm-toolkit-meter/pom.xml | 31 ++ .../apm/toolkit/meter/BaseBuilder.java | 33 +++ .../apm/toolkit/meter/BaseMeter.java | 38 +++ .../skywalking/apm/toolkit/meter/Counter.java | 51 ++++ .../skywalking/apm/toolkit/meter/Gauge.java | 34 +++ .../apm/toolkit/meter/Histogram.java | 69 +++++ .../apm/toolkit/meter/MeterFactory.java | 71 +++++ .../skywalking/apm/toolkit/meter/MeterId.java | 128 +++++++++ .../toolkit/meter/impl/AbstractBuilder.java | 98 +++++++ .../apm/toolkit/meter/impl/AbstractMeter.java | 72 +++++ .../apm/toolkit/meter/impl/CounterImpl.java | 117 ++++++++ .../apm/toolkit/meter/impl/GaugeImpl.java | 79 +++++ .../apm/toolkit/meter/impl/HistogramImpl.java | 209 ++++++++++++++ .../apm/toolkit/meter/impl/MeterCenter.java | 65 +++++ .../apm/toolkit/meter/AbstractMeterTest.java | 194 +++++++++++++ .../apm/toolkit/meter/CounterTest.java | 117 ++++++++ .../apm/toolkit/meter/GaugeTest.java | 72 +++++ .../apm/toolkit/meter/HistogramTest.java | 131 +++++++++ .../apm/toolkit/meter/MeterCenterTest.java | 76 +++++ .../apm/toolkit/meter/MeterIdTest.java | 37 +++ .../apm-toolkit-micrometer-registry/pom.xml | 48 ++++ .../apm/meter/micrometer/MeterBuilder.java | 103 +++++++ .../meter/micrometer/SkywalkingConfig.java | 58 ++++ .../meter/micrometer/SkywalkingCounter.java | 46 +++ .../micrometer/SkywalkingCustomCounter.java | 67 +++++ .../SkywalkingDistributionSummary.java | 99 +++++++ .../micrometer/SkywalkingLongTaskTimer.java | 46 +++ .../micrometer/SkywalkingMeterRegistry.java | 187 ++++++++++++ .../apm/meter/micrometer/SkywalkingTimer.java | 99 +++++++ .../meter/micrometer/MeterBuilderTest.java | 92 ++++++ .../micrometer/SkywalkingCounterTest.java | 74 +++++ .../SkywalkingCustomCounterTest.java | 43 +++ .../SkywalkingDistributionSummaryTest.java | 88 ++++++ .../SkywalkingLongTaskTimerTest.java | 89 ++++++ .../micrometer/SkywalkingMeterBaseTest.java | 91 ++++++ .../SkywalkingMeterRegistryTest.java | 241 ++++++++++++++++ .../meter/micrometer/SkywalkingTimerTest.java | 92 ++++++ apm-application-toolkit/pom.xml | 2 + apm-protocol/apm-network/src/main/proto | 2 +- .../apm/agent/core/conf/Config.java | 17 ++ .../apm/agent/core/meter/MeterId.java | 84 ++++++ .../apm/agent/core/meter/MeterService.java | 190 ++++++++++++ .../apm/agent/core/meter/MeterTag.java | 62 ++++ .../apm/agent/core/meter/MeterType.java | 25 ++ .../core/meter/adapter/CounterAdapter.java | 23 ++ .../core/meter/adapter/GaugeAdapter.java | 23 ++ .../core/meter/adapter/HistogramAdapter.java | 33 +++ .../core/meter/adapter/MeterAdapter.java | 28 ++ .../meter/transform/CounterTransformer.java | 49 ++++ .../meter/transform/GaugeTransformer.java | 57 ++++ .../meter/transform/HistogramTransformer.java | 92 ++++++ .../meter/transform/MeterTransformer.java | 93 ++++++ ...skywalking.apm.agent.core.boot.BootService | 1 + .../agent/core/boot/ServiceManagerTest.java | 10 +- .../apm/agent/core/meter/MeterIdTest.java | 62 ++++ .../agent/core/meter/MeterServiceTest.java | 271 ++++++++++++++++++ .../apm/agent/core/meter/MeterTagTest.java | 38 +++ .../transform/CounterTransformerTest.java | 95 ++++++ .../meter/transform/GaugeTransformerTest.java | 90 ++++++ .../transform/HistogramTransformerTest.java | 119 ++++++++ .../apm-toolkit-meter-activation/pom.xml | 38 +++ .../activation/meter/CounterActivation.java | 59 ++++ .../activation/meter/CounterInterceptor.java | 45 +++ .../activation/meter/GaugeActivation.java | 59 ++++ .../activation/meter/GaugeInterceptor.java | 45 +++ .../activation/meter/HistogramActivation.java | 59 ++++ .../meter/HistogramInterceptor.java | 46 +++ .../meter/adapter/TookitCounterAdapter.java | 46 +++ .../meter/adapter/ToolkitGaugeAdapter.java | 46 +++ .../adapter/ToolkitHistogramAdapter.java | 61 ++++ .../meter/util/MeterIdConverter.java | 57 ++++ .../src/main/resources/skywalking-plugin.def | 19 ++ .../meter/CounterInterceptorTest.java | 81 ++++++ .../meter/GaugeInterceptorTest.java | 83 ++++++ .../meter/HistogramInterceptorTest.java | 83 ++++++ .../meter/ToolkitCounterAdapterTest.java | 71 +++++ .../meter/ToolkitGaugeAdapterTest.java | 49 ++++ .../meter/ToolkitHistogramAdapterTest.java | 64 +++++ apm-sniffer/apm-toolkit-activation/pom.xml | 1 + .../java-agent/Application-toolkit-meter.md | 46 +++ .../Application-toolkit-micrometer.md | 55 ++++ .../setup/service-agent/java-agent/README.md | 9 +- 82 files changed, 5868 insertions(+), 5 deletions(-) create mode 100644 apm-application-toolkit/apm-toolkit-meter/pom.xml create mode 100644 apm-application-toolkit/apm-toolkit-meter/src/main/java/org/apache/skywalking/apm/toolkit/meter/BaseBuilder.java create mode 100644 apm-application-toolkit/apm-toolkit-meter/src/main/java/org/apache/skywalking/apm/toolkit/meter/BaseMeter.java create mode 100644 apm-application-toolkit/apm-toolkit-meter/src/main/java/org/apache/skywalking/apm/toolkit/meter/Counter.java create mode 100644 apm-application-toolkit/apm-toolkit-meter/src/main/java/org/apache/skywalking/apm/toolkit/meter/Gauge.java create mode 100644 apm-application-toolkit/apm-toolkit-meter/src/main/java/org/apache/skywalking/apm/toolkit/meter/Histogram.java create mode 100644 apm-application-toolkit/apm-toolkit-meter/src/main/java/org/apache/skywalking/apm/toolkit/meter/MeterFactory.java create mode 100644 apm-application-toolkit/apm-toolkit-meter/src/main/java/org/apache/skywalking/apm/toolkit/meter/MeterId.java create mode 100644 apm-application-toolkit/apm-toolkit-meter/src/main/java/org/apache/skywalking/apm/toolkit/meter/impl/AbstractBuilder.java create mode 100644 apm-application-toolkit/apm-toolkit-meter/src/main/java/org/apache/skywalking/apm/toolkit/meter/impl/AbstractMeter.java create mode 100644 apm-application-toolkit/apm-toolkit-meter/src/main/java/org/apache/skywalking/apm/toolkit/meter/impl/CounterImpl.java create mode 100644 apm-application-toolkit/apm-toolkit-meter/src/main/java/org/apache/skywalking/apm/toolkit/meter/impl/GaugeImpl.java create mode 100644 apm-application-toolkit/apm-toolkit-meter/src/main/java/org/apache/skywalking/apm/toolkit/meter/impl/HistogramImpl.java create mode 100644 apm-application-toolkit/apm-toolkit-meter/src/main/java/org/apache/skywalking/apm/toolkit/meter/impl/MeterCenter.java create mode 100644 apm-application-toolkit/apm-toolkit-meter/src/test/java/org/apache/skywalking/apm/toolkit/meter/AbstractMeterTest.java create mode 100644 apm-application-toolkit/apm-toolkit-meter/src/test/java/org/apache/skywalking/apm/toolkit/meter/CounterTest.java create mode 100644 apm-application-toolkit/apm-toolkit-meter/src/test/java/org/apache/skywalking/apm/toolkit/meter/GaugeTest.java create mode 100644 apm-application-toolkit/apm-toolkit-meter/src/test/java/org/apache/skywalking/apm/toolkit/meter/HistogramTest.java create mode 100644 apm-application-toolkit/apm-toolkit-meter/src/test/java/org/apache/skywalking/apm/toolkit/meter/MeterCenterTest.java create mode 100644 apm-application-toolkit/apm-toolkit-meter/src/test/java/org/apache/skywalking/apm/toolkit/meter/MeterIdTest.java create mode 100644 apm-application-toolkit/apm-toolkit-micrometer-registry/pom.xml create mode 100644 apm-application-toolkit/apm-toolkit-micrometer-registry/src/main/java/org/apache/skywalking/apm/meter/micrometer/MeterBuilder.java create mode 100644 apm-application-toolkit/apm-toolkit-micrometer-registry/src/main/java/org/apache/skywalking/apm/meter/micrometer/SkywalkingConfig.java create mode 100644 apm-application-toolkit/apm-toolkit-micrometer-registry/src/main/java/org/apache/skywalking/apm/meter/micrometer/SkywalkingCounter.java create mode 100644 apm-application-toolkit/apm-toolkit-micrometer-registry/src/main/java/org/apache/skywalking/apm/meter/micrometer/SkywalkingCustomCounter.java create mode 100644 apm-application-toolkit/apm-toolkit-micrometer-registry/src/main/java/org/apache/skywalking/apm/meter/micrometer/SkywalkingDistributionSummary.java create mode 100644 apm-application-toolkit/apm-toolkit-micrometer-registry/src/main/java/org/apache/skywalking/apm/meter/micrometer/SkywalkingLongTaskTimer.java create mode 100644 apm-application-toolkit/apm-toolkit-micrometer-registry/src/main/java/org/apache/skywalking/apm/meter/micrometer/SkywalkingMeterRegistry.java create mode 100644 apm-application-toolkit/apm-toolkit-micrometer-registry/src/main/java/org/apache/skywalking/apm/meter/micrometer/SkywalkingTimer.java create mode 100644 apm-application-toolkit/apm-toolkit-micrometer-registry/src/test/java/org/apache/skywalking/apm/meter/micrometer/MeterBuilderTest.java create mode 100644 apm-application-toolkit/apm-toolkit-micrometer-registry/src/test/java/org/apache/skywalking/apm/meter/micrometer/SkywalkingCounterTest.java create mode 100644 apm-application-toolkit/apm-toolkit-micrometer-registry/src/test/java/org/apache/skywalking/apm/meter/micrometer/SkywalkingCustomCounterTest.java create mode 100644 apm-application-toolkit/apm-toolkit-micrometer-registry/src/test/java/org/apache/skywalking/apm/meter/micrometer/SkywalkingDistributionSummaryTest.java create mode 100644 apm-application-toolkit/apm-toolkit-micrometer-registry/src/test/java/org/apache/skywalking/apm/meter/micrometer/SkywalkingLongTaskTimerTest.java create mode 100644 apm-application-toolkit/apm-toolkit-micrometer-registry/src/test/java/org/apache/skywalking/apm/meter/micrometer/SkywalkingMeterBaseTest.java create mode 100644 apm-application-toolkit/apm-toolkit-micrometer-registry/src/test/java/org/apache/skywalking/apm/meter/micrometer/SkywalkingMeterRegistryTest.java create mode 100644 apm-application-toolkit/apm-toolkit-micrometer-registry/src/test/java/org/apache/skywalking/apm/meter/micrometer/SkywalkingTimerTest.java create mode 100644 apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/meter/MeterId.java create mode 100644 apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/meter/MeterService.java create mode 100644 apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/meter/MeterTag.java create mode 100644 apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/meter/MeterType.java create mode 100644 apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/meter/adapter/CounterAdapter.java create mode 100644 apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/meter/adapter/GaugeAdapter.java create mode 100644 apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/meter/adapter/HistogramAdapter.java create mode 100644 apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/meter/adapter/MeterAdapter.java create mode 100644 apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/meter/transform/CounterTransformer.java create mode 100644 apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/meter/transform/GaugeTransformer.java create mode 100644 apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/meter/transform/HistogramTransformer.java create mode 100644 apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/meter/transform/MeterTransformer.java create mode 100644 apm-sniffer/apm-agent-core/src/test/java/org/apache/skywalking/apm/agent/core/meter/MeterIdTest.java create mode 100644 apm-sniffer/apm-agent-core/src/test/java/org/apache/skywalking/apm/agent/core/meter/MeterServiceTest.java create mode 100644 apm-sniffer/apm-agent-core/src/test/java/org/apache/skywalking/apm/agent/core/meter/MeterTagTest.java create mode 100644 apm-sniffer/apm-agent-core/src/test/java/org/apache/skywalking/apm/agent/core/meter/transform/CounterTransformerTest.java create mode 100644 apm-sniffer/apm-agent-core/src/test/java/org/apache/skywalking/apm/agent/core/meter/transform/GaugeTransformerTest.java create mode 100644 apm-sniffer/apm-agent-core/src/test/java/org/apache/skywalking/apm/agent/core/meter/transform/HistogramTransformerTest.java create mode 100644 apm-sniffer/apm-toolkit-activation/apm-toolkit-meter-activation/pom.xml create mode 100644 apm-sniffer/apm-toolkit-activation/apm-toolkit-meter-activation/src/main/java/org/apache/skywalking/apm/toolkit/activation/meter/CounterActivation.java create mode 100644 apm-sniffer/apm-toolkit-activation/apm-toolkit-meter-activation/src/main/java/org/apache/skywalking/apm/toolkit/activation/meter/CounterInterceptor.java create mode 100644 apm-sniffer/apm-toolkit-activation/apm-toolkit-meter-activation/src/main/java/org/apache/skywalking/apm/toolkit/activation/meter/GaugeActivation.java create mode 100644 apm-sniffer/apm-toolkit-activation/apm-toolkit-meter-activation/src/main/java/org/apache/skywalking/apm/toolkit/activation/meter/GaugeInterceptor.java create mode 100644 apm-sniffer/apm-toolkit-activation/apm-toolkit-meter-activation/src/main/java/org/apache/skywalking/apm/toolkit/activation/meter/HistogramActivation.java create mode 100644 apm-sniffer/apm-toolkit-activation/apm-toolkit-meter-activation/src/main/java/org/apache/skywalking/apm/toolkit/activation/meter/HistogramInterceptor.java create mode 100644 apm-sniffer/apm-toolkit-activation/apm-toolkit-meter-activation/src/main/java/org/apache/skywalking/apm/toolkit/activation/meter/adapter/TookitCounterAdapter.java create mode 100644 apm-sniffer/apm-toolkit-activation/apm-toolkit-meter-activation/src/main/java/org/apache/skywalking/apm/toolkit/activation/meter/adapter/ToolkitGaugeAdapter.java create mode 100644 apm-sniffer/apm-toolkit-activation/apm-toolkit-meter-activation/src/main/java/org/apache/skywalking/apm/toolkit/activation/meter/adapter/ToolkitHistogramAdapter.java create mode 100644 apm-sniffer/apm-toolkit-activation/apm-toolkit-meter-activation/src/main/java/org/apache/skywalking/apm/toolkit/activation/meter/util/MeterIdConverter.java create mode 100644 apm-sniffer/apm-toolkit-activation/apm-toolkit-meter-activation/src/main/resources/skywalking-plugin.def create mode 100644 apm-sniffer/apm-toolkit-activation/apm-toolkit-meter-activation/src/test/java/org/apache/skywalking/apm/toolkit/activation/meter/CounterInterceptorTest.java create mode 100644 apm-sniffer/apm-toolkit-activation/apm-toolkit-meter-activation/src/test/java/org/apache/skywalking/apm/toolkit/activation/meter/GaugeInterceptorTest.java create mode 100644 apm-sniffer/apm-toolkit-activation/apm-toolkit-meter-activation/src/test/java/org/apache/skywalking/apm/toolkit/activation/meter/HistogramInterceptorTest.java create mode 100644 apm-sniffer/apm-toolkit-activation/apm-toolkit-meter-activation/src/test/java/org/apache/skywalking/apm/toolkit/meter/ToolkitCounterAdapterTest.java create mode 100644 apm-sniffer/apm-toolkit-activation/apm-toolkit-meter-activation/src/test/java/org/apache/skywalking/apm/toolkit/meter/ToolkitGaugeAdapterTest.java create mode 100644 apm-sniffer/apm-toolkit-activation/apm-toolkit-meter-activation/src/test/java/org/apache/skywalking/apm/toolkit/meter/ToolkitHistogramAdapterTest.java create mode 100644 docs/en/setup/service-agent/java-agent/Application-toolkit-meter.md create mode 100644 docs/en/setup/service-agent/java-agent/Application-toolkit-micrometer.md diff --git a/apm-application-toolkit/apm-toolkit-meter/pom.xml b/apm-application-toolkit/apm-toolkit-meter/pom.xml new file mode 100644 index 0000000000..48c0c7fe2a --- /dev/null +++ b/apm-application-toolkit/apm-toolkit-meter/pom.xml @@ -0,0 +1,31 @@ + + + + + apm-application-toolkit + org.apache.skywalking + 8.1.0-SNAPSHOT + + 4.0.0 + + apm-toolkit-meter + jar + + http://maven.apache.org + diff --git a/apm-application-toolkit/apm-toolkit-meter/src/main/java/org/apache/skywalking/apm/toolkit/meter/BaseBuilder.java b/apm-application-toolkit/apm-toolkit-meter/src/main/java/org/apache/skywalking/apm/toolkit/meter/BaseBuilder.java new file mode 100644 index 0000000000..d73ddff9b0 --- /dev/null +++ b/apm-application-toolkit/apm-toolkit-meter/src/main/java/org/apache/skywalking/apm/toolkit/meter/BaseBuilder.java @@ -0,0 +1,33 @@ +/* + * 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.apm.toolkit.meter; + +public interface BaseBuilder { + + /** + * append new tags to this meter + */ + Builder tag(String name, String value); + + /** + * Build a new meter object + */ + Meter build(); + +} diff --git a/apm-application-toolkit/apm-toolkit-meter/src/main/java/org/apache/skywalking/apm/toolkit/meter/BaseMeter.java b/apm-application-toolkit/apm-toolkit-meter/src/main/java/org/apache/skywalking/apm/toolkit/meter/BaseMeter.java new file mode 100644 index 0000000000..de7a2a0f57 --- /dev/null +++ b/apm-application-toolkit/apm-toolkit-meter/src/main/java/org/apache/skywalking/apm/toolkit/meter/BaseMeter.java @@ -0,0 +1,38 @@ +/* + * 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.apm.toolkit.meter; + +public interface BaseMeter { + + /** + * Get meter name + */ + String getName(); + + /** + * Get tag value + */ + String getTag(String tagKey); + + /** + * Get meter Id + */ + MeterId getMeterId(); + +} diff --git a/apm-application-toolkit/apm-toolkit-meter/src/main/java/org/apache/skywalking/apm/toolkit/meter/Counter.java b/apm-application-toolkit/apm-toolkit-meter/src/main/java/org/apache/skywalking/apm/toolkit/meter/Counter.java new file mode 100644 index 0000000000..fb7cd73524 --- /dev/null +++ b/apm-application-toolkit/apm-toolkit-meter/src/main/java/org/apache/skywalking/apm/toolkit/meter/Counter.java @@ -0,0 +1,51 @@ +/* + * 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.apm.toolkit.meter; + +/** + * A counter is a cumulative metric that represents a single monotonically increasing counter whose value can only increase or be reset to zero on restart. + */ +public interface Counter extends BaseMeter { + + void increment(double count); + + double get(); + + /** + * Counter mode + */ + enum Mode { + /** + * Increase single value, report the real value + */ + INCREMENT, + + /** + * Rate with previous value when report + */ + RATE + } + + interface Builder extends BaseBuilder { + /** + * Setting counter mode + */ + Builder mode(Mode mode); + } +} diff --git a/apm-application-toolkit/apm-toolkit-meter/src/main/java/org/apache/skywalking/apm/toolkit/meter/Gauge.java b/apm-application-toolkit/apm-toolkit-meter/src/main/java/org/apache/skywalking/apm/toolkit/meter/Gauge.java new file mode 100644 index 0000000000..fd2d2ae0c5 --- /dev/null +++ b/apm-application-toolkit/apm-toolkit-meter/src/main/java/org/apache/skywalking/apm/toolkit/meter/Gauge.java @@ -0,0 +1,34 @@ +/* + * 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.apm.toolkit.meter; + +/** + * A gauge is a metric that represents a single numerical value that can arbitrarily go up and down. + */ +public interface Gauge extends BaseMeter { + + /** + * Get count + */ + double get(); + + interface Builder extends BaseBuilder { + } + +} diff --git a/apm-application-toolkit/apm-toolkit-meter/src/main/java/org/apache/skywalking/apm/toolkit/meter/Histogram.java b/apm-application-toolkit/apm-toolkit-meter/src/main/java/org/apache/skywalking/apm/toolkit/meter/Histogram.java new file mode 100644 index 0000000000..5869052e04 --- /dev/null +++ b/apm-application-toolkit/apm-toolkit-meter/src/main/java/org/apache/skywalking/apm/toolkit/meter/Histogram.java @@ -0,0 +1,69 @@ +/* + * 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.apm.toolkit.meter; + +import java.util.List; + +/** + * Similar to a histogram, a summary sample observations (usual things like request durations and response sizes). + * While it also provides a total count of observations and a sum of all observed values, it calculates configurable quartiles over a sliding time window. + * The histogram provides detailed data in each data group. + */ +public interface Histogram extends BaseMeter { + + /** + * Add value into the histogram, automatic analyze what bucket count need to be increment + * [step1, step2) + */ + void addValue(double value); + + /** + * Get all buckets + */ + Bucket[] getBuckets(); + + interface Builder extends BaseBuilder { + + /** + * Setting bucket steps + */ + Builder steps(List steps); + + /** + * Setting min value, default is zero + */ + Builder minValue(double minValue); + } + + /** + * Histogram bucket + */ + interface Bucket { + + /** + * Get bucket key + */ + double getBucket(); + + /** + * Get bucket count + */ + long getCount(); + } +} diff --git a/apm-application-toolkit/apm-toolkit-meter/src/main/java/org/apache/skywalking/apm/toolkit/meter/MeterFactory.java b/apm-application-toolkit/apm-toolkit-meter/src/main/java/org/apache/skywalking/apm/toolkit/meter/MeterFactory.java new file mode 100644 index 0000000000..60d2611085 --- /dev/null +++ b/apm-application-toolkit/apm-toolkit-meter/src/main/java/org/apache/skywalking/apm/toolkit/meter/MeterFactory.java @@ -0,0 +1,71 @@ +/* + * 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.apm.toolkit.meter; + +import org.apache.skywalking.apm.toolkit.meter.impl.CounterImpl; +import org.apache.skywalking.apm.toolkit.meter.impl.GaugeImpl; +import org.apache.skywalking.apm.toolkit.meter.impl.HistogramImpl; + +import java.util.function.Supplier; + +public class MeterFactory { + + /** + * Create a counter builder by name + */ + public static Counter.Builder counter(String name) { + return new CounterImpl.Builder(name); + } + + /** + * Create a counter builder by meter id + */ + public static Counter.Builder counter(MeterId meterId) { + return new CounterImpl.Builder(meterId); + } + + /** + * Create a gauge builder by name and getter + */ + public static Gauge.Builder gauge(String name, Supplier getter) { + return new GaugeImpl.Builder(name, getter); + } + + /** + * Create a gauge builder by meter id and getter + */ + public static Gauge.Builder gauge(MeterId meterId, Supplier getter) { + return new GaugeImpl.Builder(meterId, getter); + } + + /** + * Create a histogram builder by name + */ + public static Histogram.Builder histogram(String name) { + return new HistogramImpl.Builder(name); + } + + /** + * Create a histogram builder by meterId + */ + public static Histogram.Builder histogram(MeterId meterId) { + return new HistogramImpl.Builder(meterId); + } + +} diff --git a/apm-application-toolkit/apm-toolkit-meter/src/main/java/org/apache/skywalking/apm/toolkit/meter/MeterId.java b/apm-application-toolkit/apm-toolkit-meter/src/main/java/org/apache/skywalking/apm/toolkit/meter/MeterId.java new file mode 100644 index 0000000000..34949ce326 --- /dev/null +++ b/apm-application-toolkit/apm-toolkit-meter/src/main/java/org/apache/skywalking/apm/toolkit/meter/MeterId.java @@ -0,0 +1,128 @@ +/* + * 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.apm.toolkit.meter; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * Meter identity + */ +public class MeterId { + + protected final String name; + protected final MeterType type; + protected final List tags = new ArrayList<>(); + + public MeterId(String name, MeterType type) { + this.name = name; + this.type = type; + } + + public MeterId(String name, MeterType type, List tags) { + this.name = name; + this.type = type; + this.tags.addAll(tags); + } + + public String getName() { + return name; + } + + public MeterType getType() { + return type; + } + + public List getTags() { + return tags; + } + + /** + * Simple copy to a new meter id, change the name and type + */ + public MeterId copyTo(String name, MeterType type) { + return new MeterId(name, type, tags); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + MeterId meterId = (MeterId) o; + return Objects.equals(name, meterId.name) && + type == meterId.type && + Objects.equals(tags, meterId.tags); + } + + @Override + public int hashCode() { + return Objects.hash(name, type, tags); + } + + /** + * The meter type + */ + public static enum MeterType { + COUNTER, + GAUGE, + HISTOGRAM + } + + /** + * Using name/value pair as the tag, also it will {@link Comparable} when we sort all of tags + */ + public static class Tag implements Comparable { + private String name; + private String value; + + public Tag(String name, String value) { + this.name = name; + this.value = value; + } + + public String getName() { + return name; + } + + public String getValue() { + return value; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Tag tag = (Tag) o; + return Objects.equals(name, tag.name) && + Objects.equals(value, tag.value); + } + + @Override + public int hashCode() { + return Objects.hash(name, value); + } + + @Override + public int compareTo(Tag o) { + return this.name.compareTo(o.name); + } + } + +} diff --git a/apm-application-toolkit/apm-toolkit-meter/src/main/java/org/apache/skywalking/apm/toolkit/meter/impl/AbstractBuilder.java b/apm-application-toolkit/apm-toolkit-meter/src/main/java/org/apache/skywalking/apm/toolkit/meter/impl/AbstractBuilder.java new file mode 100644 index 0000000000..3c92aa2246 --- /dev/null +++ b/apm-application-toolkit/apm-toolkit-meter/src/main/java/org/apache/skywalking/apm/toolkit/meter/impl/AbstractBuilder.java @@ -0,0 +1,98 @@ +/* + * 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.apm.toolkit.meter.impl; + +import org.apache.skywalking.apm.toolkit.meter.BaseBuilder; +import org.apache.skywalking.apm.toolkit.meter.BaseMeter; +import org.apache.skywalking.apm.toolkit.meter.MeterId; + +import java.util.Objects; + +/** + * Help to build the meter + */ +public abstract class AbstractBuilder implements BaseBuilder { + protected final MeterId meterId; + + /** + * Build a new meter build, meter name is required + */ + public AbstractBuilder(String name) { + if (name == null) { + throw new IllegalArgumentException("Meter name cannot be null"); + } + this.meterId = new MeterId(name, getType()); + } + + /** + * Build a new meter build from exists meter id + */ + public AbstractBuilder(MeterId meterId) { + if (meterId == null) { + throw new IllegalArgumentException("Meter id cannot be null"); + } + if (!Objects.equals(meterId.getType(), getType())) { + throw new IllegalArgumentException("Meter id type is not matches"); + } + this.meterId = meterId; + } + + /** + * append new tag to this meter + */ + public BUILDER tag(String name, String value) { + meterId.getTags().add(new MeterId.Tag(name, value)); + return (BUILDER) this; + } + + /** + * Create a meter + */ + protected abstract BASE create(MeterId meterId); + + /** + * Accept the new meter when register, could use it to judge histogram buckets is correct. + * It's working on the same meter id only. + * @throws IllegalArgumentException if cannot be accept, throws information + */ + protected void accept(IMPL meter) throws IllegalArgumentException { + } + + /** + * Get supported build meter type + */ + protected abstract MeterId.MeterType getType(); + + /** + * Get current meter id + */ + public MeterId getMeterId() { + return meterId; + } + + /** + * Build a new meter object + */ + public BASE build() { + // sort the tags + this.meterId.getTags().sort(MeterId.Tag::compareTo); + // create or get the meter + return MeterCenter.getOrCreateMeter(this); + } +} diff --git a/apm-application-toolkit/apm-toolkit-meter/src/main/java/org/apache/skywalking/apm/toolkit/meter/impl/AbstractMeter.java b/apm-application-toolkit/apm-toolkit-meter/src/main/java/org/apache/skywalking/apm/toolkit/meter/impl/AbstractMeter.java new file mode 100644 index 0000000000..b3746b7b03 --- /dev/null +++ b/apm-application-toolkit/apm-toolkit-meter/src/main/java/org/apache/skywalking/apm/toolkit/meter/impl/AbstractMeter.java @@ -0,0 +1,72 @@ +/* + * 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.apm.toolkit.meter.impl; + +import org.apache.skywalking.apm.toolkit.meter.MeterId; + +import java.util.Objects; + +/** + * Base meter implementation bean + */ +public abstract class AbstractMeter { + + protected final MeterId meterId; + + public AbstractMeter(MeterId meterId) { + this.meterId = meterId; + } + + /** + * Get meter name + */ + public String getName() { + return meterId.getName(); + } + + /** + * Get tag value + */ + public String getTag(String tagKey) { + for (MeterId.Tag tag : meterId.getTags()) { + if (tag.getName().equals(tagKey)) { + return tag.getValue(); + } + } + return null; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + AbstractMeter abstractMeter = (AbstractMeter) o; + return Objects.equals(meterId, abstractMeter.meterId); + } + + public MeterId getMeterId() { + return meterId; + } + + @Override + public int hashCode() { + return Objects.hash(meterId); + } + +} diff --git a/apm-application-toolkit/apm-toolkit-meter/src/main/java/org/apache/skywalking/apm/toolkit/meter/impl/CounterImpl.java b/apm-application-toolkit/apm-toolkit-meter/src/main/java/org/apache/skywalking/apm/toolkit/meter/impl/CounterImpl.java new file mode 100644 index 0000000000..3a7ee41b0e --- /dev/null +++ b/apm-application-toolkit/apm-toolkit-meter/src/main/java/org/apache/skywalking/apm/toolkit/meter/impl/CounterImpl.java @@ -0,0 +1,117 @@ +/* + * 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.apm.toolkit.meter.impl; + +import org.apache.skywalking.apm.toolkit.meter.Counter; +import org.apache.skywalking.apm.toolkit.meter.MeterId; + +import java.util.Objects; +import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.atomic.DoubleAdder; + +/** + * Using {@link DoubleAdder} to add count + */ +public class CounterImpl extends AbstractMeter implements Counter { + + protected final DoubleAdder count; + protected final Mode mode; + + private final AtomicReference previous = new AtomicReference(); + + protected CounterImpl(MeterId meterId, Mode mode) { + super(meterId); + this.count = new DoubleAdder(); + this.mode = mode; + } + + /** + * Increment count + */ + public void increment(double count) { + this.count.add(count); + } + + /** + * Get count value + */ + public double get() { + return this.count.doubleValue(); + } + + /** + * Using at the Skywalking agent get, make is support {@link Mode#RATE} + */ + public double agentGet() { + final double currentValue = get(); + double count; + if (Objects.equals(Mode.RATE, this.mode)) { + final Double previousValue = previous.getAndSet(currentValue); + + // calculate the add count + if (previousValue == null) { + count = currentValue; + } else { + count = currentValue - previousValue; + } + } else { + count = currentValue; + } + + return count; + } + + public static class Builder extends AbstractBuilder implements Counter.Builder { + private Counter.Mode mode = Counter.Mode.INCREMENT; + + public Builder(String name) { + super(name); + } + + public Builder(MeterId meterId) { + super(meterId); + } + + /** + * Setting counter mode + */ + public Builder mode(Counter.Mode mode) { + this.mode = mode; + return this; + } + + @Override + protected void accept(CounterImpl meter) throws IllegalArgumentException { + // Rate mode must be same + if (!Objects.equals(meter.mode, this.mode)) { + throw new IllegalArgumentException("Mode is not same"); + } + } + + @Override + protected Counter create(MeterId meterId) { + return new CounterImpl(meterId, mode); + } + + @Override + protected MeterId.MeterType getType() { + return MeterId.MeterType.COUNTER; + } + } +} diff --git a/apm-application-toolkit/apm-toolkit-meter/src/main/java/org/apache/skywalking/apm/toolkit/meter/impl/GaugeImpl.java b/apm-application-toolkit/apm-toolkit-meter/src/main/java/org/apache/skywalking/apm/toolkit/meter/impl/GaugeImpl.java new file mode 100644 index 0000000000..0474398c4b --- /dev/null +++ b/apm-application-toolkit/apm-toolkit-meter/src/main/java/org/apache/skywalking/apm/toolkit/meter/impl/GaugeImpl.java @@ -0,0 +1,79 @@ +/* + * 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.apm.toolkit.meter.impl; + +import org.apache.skywalking.apm.toolkit.meter.Gauge; +import org.apache.skywalking.apm.toolkit.meter.MeterId; + +import java.util.function.Supplier; + +/** + * Using {@link Supplier} as the value. + */ +public class GaugeImpl extends AbstractMeter implements Gauge { + + protected Supplier getter; + + public GaugeImpl(MeterId meterId, Supplier getter) { + super(meterId); + this.getter = getter; + } + + /** + * Get count + */ + public double get() { + return getter.get(); + } + + public static class Builder extends AbstractBuilder implements Gauge.Builder { + private final Supplier getter; + + public Builder(String name, Supplier getter) { + super(name); + this.getter = getter; + } + + public Builder(MeterId meterId, Supplier getter) { + super(meterId); + this.getter = getter; + } + + @Override + public void accept(GaugeImpl meter) { + if (this.getter != meter.getter) { + throw new IllegalArgumentException("Getter is not same"); + } + } + + @Override + public GaugeImpl create(MeterId meterId) { + if (getter == null) { + throw new IllegalArgumentException("getter cannot be null"); + } + return new GaugeImpl(meterId, getter); + } + + @Override + public MeterId.MeterType getType() { + return MeterId.MeterType.GAUGE; + } + } + +} diff --git a/apm-application-toolkit/apm-toolkit-meter/src/main/java/org/apache/skywalking/apm/toolkit/meter/impl/HistogramImpl.java b/apm-application-toolkit/apm-toolkit-meter/src/main/java/org/apache/skywalking/apm/toolkit/meter/impl/HistogramImpl.java new file mode 100644 index 0000000000..baa5d1f877 --- /dev/null +++ b/apm-application-toolkit/apm-toolkit-meter/src/main/java/org/apache/skywalking/apm/toolkit/meter/impl/HistogramImpl.java @@ -0,0 +1,209 @@ +/* + * 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.apm.toolkit.meter.impl; + +import org.apache.skywalking.apm.toolkit.meter.Histogram; +import org.apache.skywalking.apm.toolkit.meter.MeterId; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Collectors; + +/** + * Custom bucket as the steps. + */ +public class HistogramImpl extends AbstractMeter implements Histogram { + + protected final Bucket[] buckets; + protected final List steps; + + public static HistogramImpl.Builder create(String name) { + return new Builder(name); + } + + protected HistogramImpl(MeterId meterId, List steps) { + super(meterId); + this.steps = steps; + this.buckets = initBuckets(steps); + } + + /** + * Add value into the histogram, automatic analyze what bucket count need to be increment + * [step1, step2) + */ + public void addValue(double value) { + Bucket bucket = findBucket(value); + if (bucket == null) { + return; + } + + bucket.increment(1L); + } + + /** + * Getting all buckets + */ + public Bucket[] getBuckets() { + return buckets; + } + + /** + * Using binary search the bucket + */ + private Bucket findBucket(double value) { + int low = 0; + int high = buckets.length - 1; + + while (low <= high) { + int mid = (low + high) / 2; + if (buckets[mid].bucket < value) + low = mid + 1; + else if (buckets[mid].bucket > value) + high = mid - 1; + else + return buckets[mid]; + } + + // because using min value as bucket, need using previous bucket + low -= 1; + + return low < buckets.length && low >= 0 ? buckets[low] : null; + } + + private Bucket[] initBuckets(List buckets) { + final Bucket[] list = new Bucket[buckets.size()]; + for (int i = 0; i < buckets.size(); i++) { + list[i] = new Bucket(buckets.get(i)); + } + return list; + } + + /** + * Histogram builder + */ + public static class Builder extends AbstractBuilder implements Histogram.Builder { + private double minValue = 0; + private List steps; + + public Builder(String name) { + super(name); + } + + public Builder(MeterId meterId) { + super(meterId); + } + + /** + * Setting bucket steps + */ + public Builder steps(List steps) { + this.steps = new ArrayList<>(steps); + return this; + } + + /** + * Setting min value, default is zero + */ + public Builder minValue(double minValue) { + this.minValue = minValue; + return this; + } + + @Override + public void accept(HistogramImpl meter) { + if (this.steps.get(0) != minValue) { + this.steps.add(0, minValue); + } + if (meter.buckets.length != this.steps.size()) { + throw new IllegalArgumentException("Steps are not has the same size"); + } + List meterSteps = new ArrayList<>(meter.buckets.length); + for (Bucket bucket : meter.buckets) { + meterSteps.add(bucket.bucket); + } + if (!Objects.equals(meterSteps, this.steps)) { + throw new IllegalArgumentException("Steps are not the same"); + } + } + + @Override + public HistogramImpl create(MeterId meterId) { + if (steps == null || steps.isEmpty()) { + throw new IllegalArgumentException("Missing steps setting"); + } + + // sort and distinct the steps + steps = steps.stream().distinct().sorted().collect(Collectors.toList()); + + // verify steps with except min value + if (steps.get(0) < minValue) { + throw new IllegalArgumentException("First step must bigger than min value"); + } else if (steps.get(0) != minValue) { + // add the min value to the steps + steps.add(0, minValue); + } + + return new HistogramImpl(meterId, steps); + } + + @Override + public MeterId.MeterType getType() { + return MeterId.MeterType.HISTOGRAM; + } + } + + /** + * Histogram bucket + */ + private static class Bucket implements Histogram.Bucket { + protected double bucket; + protected AtomicLong count = new AtomicLong(); + + public Bucket(double bucket) { + this.bucket = bucket; + } + + public void increment(long count) { + this.count.addAndGet(count); + } + + public double getBucket() { + return bucket; + } + + public long getCount() { + return count.get(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + Bucket bucket1 = (Bucket) o; + return bucket == bucket1.bucket; + } + + @Override + public int hashCode() { + return Objects.hash(bucket); + } + } +} diff --git a/apm-application-toolkit/apm-toolkit-meter/src/main/java/org/apache/skywalking/apm/toolkit/meter/impl/MeterCenter.java b/apm-application-toolkit/apm-toolkit-meter/src/main/java/org/apache/skywalking/apm/toolkit/meter/impl/MeterCenter.java new file mode 100644 index 0000000000..e9d538b049 --- /dev/null +++ b/apm-application-toolkit/apm-toolkit-meter/src/main/java/org/apache/skywalking/apm/toolkit/meter/impl/MeterCenter.java @@ -0,0 +1,65 @@ +/* + * 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.apm.toolkit.meter.impl; + +import org.apache.skywalking.apm.toolkit.meter.BaseBuilder; +import org.apache.skywalking.apm.toolkit.meter.BaseMeter; +import org.apache.skywalking.apm.toolkit.meter.MeterId; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Management all the meter. + */ +public class MeterCenter { + + private static final Map METER_MAP = new ConcurrentHashMap<>(); + + /** + * Get or create a new meter + * @return If already exists, it will return existed meter, otherwise it will register it. + */ + public static BASE getOrCreateMeter(AbstractBuilder builder) { + if (builder == null) { + return null; + } + return (BASE) METER_MAP.compute(builder.getMeterId(), (meterId, previous) -> { + if (previous == null) { + return builder.create(meterId); + } + + // Check previous meter is accept the new meter + builder.accept((IMPL) previous); + + return previous; + }); + } + + /** + * Remove meter + * @return Meter reference if exists + */ + public static BaseMeter removeMeter(MeterId id) { + if (id == null) { + return null; + } + return METER_MAP.remove(id); + } +} diff --git a/apm-application-toolkit/apm-toolkit-meter/src/test/java/org/apache/skywalking/apm/toolkit/meter/AbstractMeterTest.java b/apm-application-toolkit/apm-toolkit-meter/src/test/java/org/apache/skywalking/apm/toolkit/meter/AbstractMeterTest.java new file mode 100644 index 0000000000..511af95d48 --- /dev/null +++ b/apm-application-toolkit/apm-toolkit-meter/src/test/java/org/apache/skywalking/apm/toolkit/meter/AbstractMeterTest.java @@ -0,0 +1,194 @@ +/* + * 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.apm.toolkit.meter; + +import org.apache.skywalking.apm.toolkit.meter.impl.AbstractBuilder; +import org.apache.skywalking.apm.toolkit.meter.impl.AbstractMeter; +import org.junit.Assert; +import org.junit.Test; +import org.mockito.Mockito; +import org.mockito.internal.util.reflection.Whitebox; + +import java.util.Arrays; +import java.util.Collections; + +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +public class AbstractMeterTest { + + @Test + public void testBuild() { + // simple name and tags + TestCounter.Builder meterBuilder1 = Mockito.spy(TestCounter.create("test_meter")); + meterBuilder1.tag("k1", "v1"); + final Counter testCounter1 = meterBuilder1.build(); + Assert.assertNotNull(testCounter1); + + verify(meterBuilder1, times(1)).create(any()); + verify(meterBuilder1, times(0)).accept(any()); + + final MeterId meterId = (MeterId) Whitebox.getInternalState(testCounter1, "meterId"); + Assert.assertNotNull(meterId); + Assert.assertEquals(meterId.getName(), "test_meter"); + Assert.assertEquals(meterId.getType(), MeterId.MeterType.COUNTER); + Assert.assertEquals(meterId.getTags(), Arrays.asList(new MeterId.Tag("k1", "v1"))); + + // same name and tags + TestCounter.Builder meterBuilder2 = Mockito.spy(TestCounter.create("test_meter")); + meterBuilder2.tag("k1", "v1"); + final Counter testCounter2 = meterBuilder2.build(); + Assert.assertNotNull(testCounter2); + verify(meterBuilder2, times(0)).create(any()); + verify(meterBuilder2, times(1)).accept(any()); + + // empty name + try { + TestCounter.create(null).build(); + throw new RuntimeException(); + } catch (IllegalArgumentException e) { + } catch (Exception e) { + throw e; + } + + // correct meter id + final MeterId tmpMeterId = new MeterId("test_meter_builder", MeterId.MeterType.COUNTER, Collections.emptyList()); + new TestCounter.Builder(tmpMeterId).build(); + + final MeterId copiedMeterId = tmpMeterId.copyTo("test_meter_builder", MeterId.MeterType.GAUGE); + // not matched type + try { + new TestCounter.Builder(copiedMeterId).build(); + throw new RuntimeException(); + } catch (IllegalArgumentException e) { + } catch (Exception e) { + throw e; + } + + // empty meterId + try { + MeterId emptyId = null; + new TestCounter.Builder(emptyId).build(); + throw new RuntimeException(); + } catch (IllegalArgumentException e) { + } catch (Exception e) { + throw e; + } + } + + @Test + public void testEquals() { + // same name + Assert.assertEquals( + TestCounter.create("test").build(), + TestCounter.create("test").build() + ); + + // same name and tag + Assert.assertEquals( + TestCounter.create("test").tag("k1", "v1").build(), + TestCounter.create("test").tag("k1", "v1").build() + ); + + // not orders tags + Assert.assertEquals( + TestCounter.create("test").tag("k2", "v2").tag("k3", "v3").build(), + TestCounter.create("test").tag("k3", "v3").tag("k2", "v2").build() + ); + + // not same name + Assert.assertNotEquals( + TestCounter.create("test1").build(), + TestCounter.create("test2").build() + ); + + // same name, not same tag + Assert.assertNotEquals( + TestCounter.create("test3").tag("k1", "v1").build(), + TestCounter.create("test4").tag("k1", "v2").build() + ); + } + + @Test + public void testGetName() { + final Counter meter = TestCounter.create("test").build(); + Assert.assertEquals(meter.getName(), "test"); + } + + @Test + public void testGetTag() { + final Counter meter = TestCounter.create("test").tag("k1", "v1").build(); + Assert.assertEquals(meter.getTag("k1"), "v1"); + Assert.assertNull(meter.getTag("k2")); + } + + @Test + public void testGetMeterId() { + final Counter meter = TestCounter.create("test").tag("k1", "v1").build(); + final MeterId tmpMeterId = new MeterId("test", MeterId.MeterType.COUNTER, + Arrays.asList(new MeterId.Tag("k1", "v1"))); + final TestCounter counter = (TestCounter) meter; + Assert.assertEquals(tmpMeterId, counter.getMeterId()); + } + + private static class TestCounter extends AbstractMeter implements Counter { + private TestCounter(MeterId meterId) { + super(meterId); + } + + public static TestCounter.Builder create(String name) { + return new Builder(name); + } + + @Override + public void increment(double count) { + } + + @Override + public double get() { + return 0; + } + + public static class Builder extends AbstractBuilder { + + public Builder(String name) { + super(name); + } + + public Builder(MeterId meterId) { + super(meterId); + } + + @Override + public TestCounter create(MeterId meterId) { + return new TestCounter(meterId); + } + + @Override + protected void accept(TestCounter meter) throws IllegalArgumentException { + } + + @Override + public MeterId.MeterType getType() { + return MeterId.MeterType.COUNTER; + } + } + } +} diff --git a/apm-application-toolkit/apm-toolkit-meter/src/test/java/org/apache/skywalking/apm/toolkit/meter/CounterTest.java b/apm-application-toolkit/apm-toolkit-meter/src/test/java/org/apache/skywalking/apm/toolkit/meter/CounterTest.java new file mode 100644 index 0000000000..572c478665 --- /dev/null +++ b/apm-application-toolkit/apm-toolkit-meter/src/test/java/org/apache/skywalking/apm/toolkit/meter/CounterTest.java @@ -0,0 +1,117 @@ +/* + * 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.apm.toolkit.meter; + +import org.apache.skywalking.apm.toolkit.meter.impl.CounterImpl; +import org.junit.Assert; +import org.junit.Test; + +import java.util.Arrays; + +public class CounterTest { + + @Test + public void testBuild() { + Counter counter1 = MeterFactory.counter("test_counter").tag("k1", "v1").build(); + Assert.assertNotNull(counter1); + + final Counter counter2 = MeterFactory.counter(new MeterId("test_counter", MeterId.MeterType.COUNTER, Arrays.asList(new MeterId.Tag("k1", "v1")))).build(); + Assert.assertNotNull(counter2); + Assert.assertEquals(counter1, counter2); + } + + @Test + public void testIncrement() { + Counter counter = MeterFactory.counter("test_counter1").tag("k1", "v1").build(); + counter.increment(1); + Assert.assertEquals(counter.get(), 1d, 0.0); + + counter.increment(1.5); + Assert.assertEquals(counter.get(), 2.5d, 0.0); + + counter.increment(-1d); + Assert.assertEquals(counter.get(), 1.5d, 0.0); + } + + @Test + public void testAccept() { + Counter counter = MeterFactory.counter("test_counter_accept") + .tag("k1", "v1") + .mode(Counter.Mode.INCREMENT) + .build(); + + // Check the same mode + try { + MeterFactory.counter("test_counter_accept") + .tag("k1", "v1") + .mode(Counter.Mode.INCREMENT) + .build(); + } catch (IllegalArgumentException e) { + throw e; + } catch (Exception e) { + } + + // Check the different mode + try { + MeterFactory.counter("test_counter_accept") + .tag("k1", "v1") + .mode(Counter.Mode.RATE) + .build(); + throw new IllegalStateException(); + } catch (IllegalStateException e) { + throw e; + } catch (Exception e) { + } + } + + @Test + public void testAgentGetWithoutRate() { + final Counter counter = MeterFactory.counter("test_counter_without_rate") + .tag("k1", "v1") + .build(); + + final CounterImpl counterImpl = (CounterImpl) counter; + counter.increment(1); + Assert.assertEquals(counterImpl.agentGet(), 1d, 0.0); + Assert.assertEquals(counterImpl.agentGet(), 1d, 0.0); + + counter.increment(1.5); + counter.increment(-0.5); + Assert.assertEquals(counterImpl.agentGet(), 2d, 0.0); + Assert.assertEquals(counterImpl.agentGet(), 2d, 0.0); + } + + @Test + public void testAgentGetWithRate() { + final Counter counter = MeterFactory.counter("test_counter_with_rate") + .tag("k1", "v1") + .mode(Counter.Mode.RATE).build(); + + final CounterImpl counterImpl = (CounterImpl) counter; + counter.increment(1); + Assert.assertEquals(counterImpl.agentGet(), 1d, 0.0); + Assert.assertEquals(counterImpl.agentGet(), 0d, 0.0); + + counter.increment(1.5); + counter.increment(-0.5); + Assert.assertEquals(counterImpl.agentGet(), 1d, 0.0); + Assert.assertEquals(counterImpl.agentGet(), 0d, 0.0); + } + +} diff --git a/apm-application-toolkit/apm-toolkit-meter/src/test/java/org/apache/skywalking/apm/toolkit/meter/GaugeTest.java b/apm-application-toolkit/apm-toolkit-meter/src/test/java/org/apache/skywalking/apm/toolkit/meter/GaugeTest.java new file mode 100644 index 0000000000..bc707d310e --- /dev/null +++ b/apm-application-toolkit/apm-toolkit-meter/src/test/java/org/apache/skywalking/apm/toolkit/meter/GaugeTest.java @@ -0,0 +1,72 @@ +/* + * 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.apm.toolkit.meter; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.Collections; + +public class GaugeTest { + + @Test + public void testBuild() { + Gauge gauge = MeterFactory.gauge("test_gauge1", () -> 1d).tag("k1", "v1").build(); + Assert.assertNotNull(gauge); + + // Same meter name and new getter + try { + MeterFactory.gauge("test_gauge1", () -> 1d).tag("k1", "v1").build(); + throw new IllegalStateException(); + } catch (IllegalStateException e) { + throw e; + } catch (Exception e) { + } + + // Missing getter reference + try { + MeterFactory.gauge("test_gauge2", null).build(); + throw new IllegalStateException(); + } catch (IllegalStateException e) { + throw e; + } catch (Exception e) { + } + + // Build by meterId + final Gauge gauge1 = MeterFactory.gauge(new MeterId("test_gauge3", MeterId.MeterType.GAUGE, Collections.emptyList()), () -> 1d).build(); + Assert.assertNotNull(gauge1); + } + + @Test + public void testGet() { + Gauge gauge = MeterFactory.gauge("test_gauge3", () -> 1d).tag("k1", "v1").build(); + Assert.assertEquals(gauge.get(), 1d, 0.0); + + // Need throw exception + gauge = MeterFactory.gauge("test_gauge4", () -> Double.valueOf(1 / 0)).build(); + try { + gauge.get(); + throw new IllegalStateException(); + } catch (IllegalStateException e) { + throw e; + } catch (Exception e) { + } + } + +} diff --git a/apm-application-toolkit/apm-toolkit-meter/src/test/java/org/apache/skywalking/apm/toolkit/meter/HistogramTest.java b/apm-application-toolkit/apm-toolkit-meter/src/test/java/org/apache/skywalking/apm/toolkit/meter/HistogramTest.java new file mode 100644 index 0000000000..89d0980ca5 --- /dev/null +++ b/apm-application-toolkit/apm-toolkit-meter/src/test/java/org/apache/skywalking/apm/toolkit/meter/HistogramTest.java @@ -0,0 +1,131 @@ +/* + * 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.apm.toolkit.meter; + +import org.apache.skywalking.apm.toolkit.meter.impl.HistogramImpl; +import org.junit.Assert; +import org.junit.Test; + +import java.util.Arrays; +import java.util.Collections; + +public class HistogramTest { + + @Test + public void testBuild() { + // normal + Histogram histogram = MeterFactory.histogram("test_histogram1").steps(Arrays.asList(1d, 5d, 10d)).minValue(-10) + .tag("k1", "v1").build(); + verifyHistogram(histogram, -10d, 0, 1d, 0, 5d, 0, 10d, 0); + + // except value bigger than first bucket + try { + histogram = HistogramImpl.create("test_histogram2").steps(Arrays.asList(1d, 5d, 10d)).minValue(2).build(); + throw new IllegalStateException("valid failed"); + } catch (IllegalStateException e) { + throw e; + } catch (Exception e) { + Assert.assertTrue(e instanceof IllegalArgumentException); + } + + // except min value is equals first step + histogram = HistogramImpl.create("test_histogram3").steps(Arrays.asList(1d, 5d, 10d)).minValue(1d) + .tag("k1", "v1").build(); + verifyHistogram(histogram, 1d, 0, 5d, 0, 10d, 0); + + // empty step + try { + HistogramImpl.create("test").build(); + throw new IllegalStateException(); + } catch (IllegalStateException e) { + throw e; + } catch (Exception e) { + Assert.assertTrue(e instanceof IllegalArgumentException); + } + + // Build by meterId + histogram = MeterFactory.histogram(new MeterId("test_histogram4", MeterId.MeterType.HISTOGRAM, Collections.emptyList())) + .steps(Arrays.asList(1d, 5d, 10d)).minValue(0d).build(); + Assert.assertNotNull(histogram); + } + + @Test + public void testAccept() { + MeterFactory.histogram("test_histogram_accept").steps(Arrays.asList(1d, 3d, 5d)).minValue(0).build(); + + // same histogram + HistogramImpl.create("test_histogram_accept").steps(Arrays.asList(1d, 3d, 5d)).minValue(0).build(); + + // not same steps size + try { + HistogramImpl.create("test_histogram_accept").steps(Arrays.asList(1d, 3d, 5d, 7d)).minValue(-1).build(); + } catch (IllegalArgumentException e) { + } catch (Exception e) { + throw e; + } + + // not same steps value + try { + HistogramImpl.create("test_histogram_accept").steps(Arrays.asList(1d, 3d, 6d)).minValue(-1).build(); + } catch (IllegalArgumentException e) { + } catch (Exception e) { + throw e; + } + } + + @Test + public void testAddValue() { + Histogram histogram = MeterFactory.histogram("test_histogram5").steps(Arrays.asList(1d, 5d, 10d)).minValue(-10) + .tag("k1", "v1").build(); + + // single value + histogram.addValue(2); + verifyHistogram(histogram, -10d, 0L, 1d, 1L, 5d, 0L, 10d, 0L); + + // multiple values + histogram.addValue(2); + histogram.addValue(2); + histogram.addValue(9); + verifyHistogram(histogram, -10d, 0L, 1d, 3L, 5d, 1L, 10d, 0L); + + // un-support value + histogram.addValue(-11); + verifyHistogram(histogram, -10d, 0L, 1d, 3L, 5d, 1L, 10d, 0L); + + // max value + histogram.addValue(Integer.MAX_VALUE); + histogram.addValue(9); + histogram.addValue(10); + verifyHistogram(histogram, -10d, 0L, 1d, 3L, 5d, 2L, 10d, 2L); + } + + /** + * Verify histogram bucket counts + */ + public static void verifyHistogram(Histogram histogram, double... buckets) { + Assert.assertNotNull(histogram); + final Histogram.Bucket[] histogramBuckets = histogram.getBuckets(); + Assert.assertEquals(histogramBuckets.length, buckets.length / 2); + for (int i = 0; i < histogramBuckets.length; i++) { + Assert.assertNotNull(buckets[i]); + Assert.assertEquals(buckets[i * 2], histogramBuckets[i].getBucket(), 0.0); + Assert.assertEquals(buckets[i * 2 + 1], histogramBuckets[i].getCount(), 0.0); + } + } +} diff --git a/apm-application-toolkit/apm-toolkit-meter/src/test/java/org/apache/skywalking/apm/toolkit/meter/MeterCenterTest.java b/apm-application-toolkit/apm-toolkit-meter/src/test/java/org/apache/skywalking/apm/toolkit/meter/MeterCenterTest.java new file mode 100644 index 0000000000..58f19ad1f1 --- /dev/null +++ b/apm-application-toolkit/apm-toolkit-meter/src/test/java/org/apache/skywalking/apm/toolkit/meter/MeterCenterTest.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.apm.toolkit.meter; + +import org.apache.skywalking.apm.toolkit.meter.impl.AbstractMeter; +import org.apache.skywalking.apm.toolkit.meter.impl.MeterCenter; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.powermock.reflect.Whitebox; + +import java.util.Collections; +import java.util.Map; + +public class MeterCenterTest { + + private Map meterMap; + + @Before + public void setup() { + meterMap = Whitebox.getInternalState(MeterCenter.class, "METER_MAP"); + meterMap.clear(); + } + + @Test + public void testCreateOrGet() { + // null + Assert.assertNull(MeterCenter.getOrCreateMeter(null)); + + // simple counter + final Counter counter = MeterFactory.counter("test").build(); + final MeterId counterMeterId = Whitebox.getInternalState(counter, "meterId"); + Assert.assertNotNull(counterMeterId); + Assert.assertNotNull(meterMap.get(counterMeterId)); + Assert.assertEquals(meterMap.get(counterMeterId), counter); + + // same counter + Assert.assertEquals(counter, MeterFactory.counter("test").build()); + Assert.assertEquals(meterMap.size(), 1); + Assert.assertNotNull(meterMap.get(counterMeterId)); + Assert.assertEquals(meterMap.get(counterMeterId), counter); + } + + @Test + public void testRemoveMeter() { + final Counter counter = MeterFactory.counter("test").build(); + Assert.assertEquals(meterMap.size(), 1); + final MeterId counterMeterId = Whitebox.getInternalState(counter, "meterId"); + MeterCenter.removeMeter(counterMeterId); + Assert.assertEquals(meterMap.size(), 0); + + // not registered meter id + final MeterId newMeterId = new MeterId("test1", MeterId.MeterType.COUNTER, Collections.emptyList()); + MeterCenter.removeMeter(newMeterId); + Assert.assertEquals(meterMap.size(), 0); + + // remove null + MeterCenter.removeMeter(null); + } +} diff --git a/apm-application-toolkit/apm-toolkit-meter/src/test/java/org/apache/skywalking/apm/toolkit/meter/MeterIdTest.java b/apm-application-toolkit/apm-toolkit-meter/src/test/java/org/apache/skywalking/apm/toolkit/meter/MeterIdTest.java new file mode 100644 index 0000000000..5d3c0a4d53 --- /dev/null +++ b/apm-application-toolkit/apm-toolkit-meter/src/test/java/org/apache/skywalking/apm/toolkit/meter/MeterIdTest.java @@ -0,0 +1,37 @@ +/* + * 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.apm.toolkit.meter; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.Arrays; + +public class MeterIdTest { + + @Test + public void testCopyTo() { + final MeterId meterId = new MeterId("test", MeterId.MeterType.COUNTER, Arrays.asList(new MeterId.Tag("k1", "v1"))); + final MeterId copied = meterId.copyTo("test_copied", MeterId.MeterType.GAUGE); + + Assert.assertEquals("test_copied", copied.getName()); + Assert.assertEquals(MeterId.MeterType.GAUGE, copied.getType()); + Assert.assertEquals(meterId.getTags(), copied.getTags()); + } +} diff --git a/apm-application-toolkit/apm-toolkit-micrometer-registry/pom.xml b/apm-application-toolkit/apm-toolkit-micrometer-registry/pom.xml new file mode 100644 index 0000000000..bb63e4d067 --- /dev/null +++ b/apm-application-toolkit/apm-toolkit-micrometer-registry/pom.xml @@ -0,0 +1,48 @@ + + + + + apm-application-toolkit + org.apache.skywalking + 8.1.0-SNAPSHOT + + 4.0.0 + + apm-toolkit-micrometer-registry + jar + + http://maven.apache.org + + + 1.5.0 + + + + + org.apache.skywalking + apm-toolkit-meter + ${project.version} + + + io.micrometer + micrometer-core + ${micrometer-core.version} + + + diff --git a/apm-application-toolkit/apm-toolkit-micrometer-registry/src/main/java/org/apache/skywalking/apm/meter/micrometer/MeterBuilder.java b/apm-application-toolkit/apm-toolkit-micrometer-registry/src/main/java/org/apache/skywalking/apm/meter/micrometer/MeterBuilder.java new file mode 100644 index 0000000000..609af84b45 --- /dev/null +++ b/apm-application-toolkit/apm-toolkit-micrometer-registry/src/main/java/org/apache/skywalking/apm/meter/micrometer/MeterBuilder.java @@ -0,0 +1,103 @@ +/* + * 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.apm.meter.micrometer; + +import io.micrometer.core.instrument.Meter; +import io.micrometer.core.instrument.distribution.DistributionStatisticConfig; +import io.micrometer.core.instrument.util.TimeUtils; +import org.apache.skywalking.apm.toolkit.meter.Counter; +import org.apache.skywalking.apm.toolkit.meter.Histogram; +import org.apache.skywalking.apm.toolkit.meter.MeterFactory; +import org.apache.skywalking.apm.toolkit.meter.MeterId; + +import java.util.List; +import java.util.NavigableSet; +import java.util.Optional; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +/** + * Help to build the meter + */ +public class MeterBuilder { + + /** + * Build the counter + */ + public static Counter buildCounter(MeterId meterId, SkywalkingConfig config) { + return MeterFactory.counter(meterId) + .mode(getCounterMode(meterId, config)) + .build(); + } + + /** + * Get counter mode + */ + public static Counter.Mode getCounterMode(MeterId meterId, SkywalkingConfig config) { + return config.isRateCounter(meterId.getName()) ? Counter.Mode.RATE : Counter.Mode.INCREMENT; + } + + /** + * Build the histogram + * @return return histogram if support + */ + public static Optional buildHistogram(MeterId meterId, boolean supportsAggregablePercentiles, + DistributionStatisticConfig distributionStatisticConfig, + boolean useNanoTime) { + if (!distributionStatisticConfig.isPublishingHistogram()) { + return Optional.empty(); + } + + final NavigableSet buckets = distributionStatisticConfig.getHistogramBuckets(supportsAggregablePercentiles); + final List steps = buckets.stream().sorted(Double::compare) + .map(t -> useNanoTime ? TimeUtils.nanosToUnit(t, TimeUnit.MILLISECONDS) : t).collect(Collectors.toList()); + + final Histogram.Builder histogramBuilder = MeterFactory.histogram( + meterId.copyTo(meterId.getName() + "_histogram", MeterId.MeterType.HISTOGRAM)).steps(steps); + final Double minimumExpectedValueAsDouble = distributionStatisticConfig.getMinimumExpectedValueAsDouble(); + if (minimumExpectedValueAsDouble != null) { + histogramBuilder.minValue(useNanoTime ? + TimeUtils.nanosToUnit(minimumExpectedValueAsDouble, TimeUnit.MILLISECONDS) : minimumExpectedValueAsDouble); + } + return Optional.of(histogramBuilder.build()); + } + + /** + * Convert micrometer {@link Meter.Id} to skywalking {@link MeterId} + */ + public static MeterId convertId(Meter.Id id, String name) { + MeterId.MeterType type; + switch (id.getType()) { + case COUNTER: + type = MeterId.MeterType.COUNTER; + break; + case GAUGE: + type = MeterId.MeterType.GAUGE; + break; + default: + // other meter need to use multiple customize meter + type = MeterId.MeterType.HISTOGRAM; + break; + } + final List tags = id.getTags().stream().map(t -> new MeterId.Tag(t.getKey(), t.getValue())).collect(Collectors.toList()); + final MeterId meterId = new MeterId(name, type, tags); + return meterId; + } + +} diff --git a/apm-application-toolkit/apm-toolkit-micrometer-registry/src/main/java/org/apache/skywalking/apm/meter/micrometer/SkywalkingConfig.java b/apm-application-toolkit/apm-toolkit-micrometer-registry/src/main/java/org/apache/skywalking/apm/meter/micrometer/SkywalkingConfig.java new file mode 100644 index 0000000000..1cfef191da --- /dev/null +++ b/apm-application-toolkit/apm-toolkit-micrometer-registry/src/main/java/org/apache/skywalking/apm/meter/micrometer/SkywalkingConfig.java @@ -0,0 +1,58 @@ +/* + * 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.apm.meter.micrometer; + +import io.micrometer.core.instrument.config.MeterRegistryConfig; + +import java.util.Collections; +import java.util.List; + +/** + * Skywalking config + */ +public class SkywalkingConfig implements MeterRegistryConfig { + + public static final SkywalkingConfig DEFAULT = new SkywalkingConfig(Collections.emptyList()); + + /** + * Supporting rate by agent side counter names + */ + private final List rateCounterNames; + + public SkywalkingConfig(List rateCounterNames) { + this.rateCounterNames = rateCounterNames; + } + + /** + * Is counter need rate by agent side + */ + public boolean isRateCounter(String name) { + return rateCounterNames == null ? false : rateCounterNames.contains(name); + } + + @Override + public String prefix() { + return ""; + } + + @Override + public String get(String key) { + return null; + } +} diff --git a/apm-application-toolkit/apm-toolkit-micrometer-registry/src/main/java/org/apache/skywalking/apm/meter/micrometer/SkywalkingCounter.java b/apm-application-toolkit/apm-toolkit-micrometer-registry/src/main/java/org/apache/skywalking/apm/meter/micrometer/SkywalkingCounter.java new file mode 100644 index 0000000000..a88700670c --- /dev/null +++ b/apm-application-toolkit/apm-toolkit-micrometer-registry/src/main/java/org/apache/skywalking/apm/meter/micrometer/SkywalkingCounter.java @@ -0,0 +1,46 @@ +/* + * 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.apm.meter.micrometer; + +import io.micrometer.core.instrument.AbstractMeter; +import org.apache.skywalking.apm.toolkit.meter.Counter; +import org.apache.skywalking.apm.toolkit.meter.impl.CounterImpl; + +/** + * Wrapper the {@link CounterImpl} to {@link io.micrometer.core.instrument.Counter} + */ +public class SkywalkingCounter extends AbstractMeter implements io.micrometer.core.instrument.Counter { + private final Counter counter; + + SkywalkingCounter(Id id, Counter counter) { + super(id); + this.counter = counter; + } + + @Override + public void increment(double amount) { + this.counter.increment(amount); + } + + @Override + public double count() { + return counter.get(); + } + +} diff --git a/apm-application-toolkit/apm-toolkit-micrometer-registry/src/main/java/org/apache/skywalking/apm/meter/micrometer/SkywalkingCustomCounter.java b/apm-application-toolkit/apm-toolkit-micrometer-registry/src/main/java/org/apache/skywalking/apm/meter/micrometer/SkywalkingCustomCounter.java new file mode 100644 index 0000000000..0efde2f1a0 --- /dev/null +++ b/apm-application-toolkit/apm-toolkit-micrometer-registry/src/main/java/org/apache/skywalking/apm/meter/micrometer/SkywalkingCustomCounter.java @@ -0,0 +1,67 @@ +/* + * 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.apm.meter.micrometer; + +import io.micrometer.core.instrument.Measurement; +import org.apache.skywalking.apm.toolkit.meter.Counter; +import org.apache.skywalking.apm.toolkit.meter.impl.AbstractBuilder; +import org.apache.skywalking.apm.toolkit.meter.impl.CounterImpl; +import org.apache.skywalking.apm.toolkit.meter.MeterId; + +/** + * Work for custom {@link Measurement}, support the skywalking rate mode + */ +public class SkywalkingCustomCounter extends CounterImpl { + + private final Measurement measurement; + + protected SkywalkingCustomCounter(MeterId meterId, Measurement measurement, SkywalkingConfig config) { + super(meterId, MeterBuilder.getCounterMode(meterId, config)); + this.measurement = measurement; + } + + @Override + public double get() { + return measurement.getValue(); + } + + /** + * Custom counter builder + */ + public static class Builder extends AbstractBuilder { + private final Measurement measurement; + private final SkywalkingConfig config; + + public Builder(MeterId meterId, Measurement measurement, SkywalkingConfig config) { + super(meterId); + this.measurement = measurement; + this.config = config; + } + + @Override + protected SkywalkingCustomCounter create(MeterId meterId) { + return new SkywalkingCustomCounter(meterId, measurement, config); + } + + @Override + protected MeterId.MeterType getType() { + return MeterId.MeterType.COUNTER; + } + } +} diff --git a/apm-application-toolkit/apm-toolkit-micrometer-registry/src/main/java/org/apache/skywalking/apm/meter/micrometer/SkywalkingDistributionSummary.java b/apm-application-toolkit/apm-toolkit-micrometer-registry/src/main/java/org/apache/skywalking/apm/meter/micrometer/SkywalkingDistributionSummary.java new file mode 100644 index 0000000000..73a47cf890 --- /dev/null +++ b/apm-application-toolkit/apm-toolkit-micrometer-registry/src/main/java/org/apache/skywalking/apm/meter/micrometer/SkywalkingDistributionSummary.java @@ -0,0 +1,99 @@ +/* + * 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.apm.meter.micrometer; + +import io.micrometer.core.instrument.AbstractDistributionSummary; +import io.micrometer.core.instrument.Clock; +import io.micrometer.core.instrument.distribution.DistributionStatisticConfig; +import org.apache.skywalking.apm.toolkit.meter.Counter; +import org.apache.skywalking.apm.toolkit.meter.Gauge; +import org.apache.skywalking.apm.toolkit.meter.Histogram; +import org.apache.skywalking.apm.toolkit.meter.MeterFactory; +import org.apache.skywalking.apm.toolkit.meter.MeterId; + +import java.util.Optional; +import java.util.concurrent.atomic.DoubleAccumulator; + +/** + * Combine the meters to {@link io.micrometer.core.instrument.DistributionSummary} + */ +public class SkywalkingDistributionSummary extends AbstractDistributionSummary { + + /** + * Summary record count + */ + private final Counter counter; + + /** + * Total summary count + */ + private final Counter sum; + + /** + * Max amount in this summary + */ + private final Gauge max; + private final DoubleAccumulator maxAdder; + + /** + * Histogram of summary + */ + private final Optional histogram; + + protected SkywalkingDistributionSummary(Id id, MeterId meterId, SkywalkingConfig config, Clock clock, + DistributionStatisticConfig distributionStatisticConfig, double scale, + boolean supportsAggregablePercentiles) { + super(id, clock, distributionStatisticConfig, scale, supportsAggregablePercentiles); + + // meter base name + String baseName = meterId.getName(); + + this.counter = MeterBuilder.buildCounter(meterId.copyTo(baseName + "_count", MeterId.MeterType.COUNTER), config); + this.sum = MeterBuilder.buildCounter(meterId.copyTo(baseName + "_sum", MeterId.MeterType.COUNTER), config); + this.maxAdder = new DoubleAccumulator((a, b) -> a > b ? a : b, 0.000); + this.max = MeterFactory.gauge(meterId.copyTo(baseName + "_max", MeterId.MeterType.GAUGE), + () -> maxAdder.doubleValue()).build(); + + this.histogram = MeterBuilder.buildHistogram(meterId, supportsAggregablePercentiles, distributionStatisticConfig, false); + } + + @Override + protected void recordNonNegative(double amount) { + counter.increment(1d); + this.sum.increment(amount); + maxAdder.accumulate(amount); + + histogram.ifPresent(h -> h.addValue(amount)); + } + + @Override + public long count() { + return (long) counter.get(); + } + + @Override + public double totalAmount() { + return sum.get(); + } + + @Override + public double max() { + return max.get(); + } +} diff --git a/apm-application-toolkit/apm-toolkit-micrometer-registry/src/main/java/org/apache/skywalking/apm/meter/micrometer/SkywalkingLongTaskTimer.java b/apm-application-toolkit/apm-toolkit-micrometer-registry/src/main/java/org/apache/skywalking/apm/meter/micrometer/SkywalkingLongTaskTimer.java new file mode 100644 index 0000000000..1ab33ff93c --- /dev/null +++ b/apm-application-toolkit/apm-toolkit-micrometer-registry/src/main/java/org/apache/skywalking/apm/meter/micrometer/SkywalkingLongTaskTimer.java @@ -0,0 +1,46 @@ +/* + * 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.apm.meter.micrometer; + +import io.micrometer.core.instrument.Clock; +import io.micrometer.core.instrument.distribution.DistributionStatisticConfig; +import io.micrometer.core.instrument.internal.DefaultLongTaskTimer; +import org.apache.skywalking.apm.toolkit.meter.MeterFactory; +import org.apache.skywalking.apm.toolkit.meter.MeterId; + +import java.util.concurrent.TimeUnit; + +/** + * Combine the meters to {@link io.micrometer.core.instrument.LongTaskTimer} + */ +public class SkywalkingLongTaskTimer extends DefaultLongTaskTimer { + + public SkywalkingLongTaskTimer(Id id, MeterId meterId, Clock clock, TimeUnit baseTimeUnit, DistributionStatisticConfig distributionStatisticConfig, boolean supportsAggregablePercentiles) { + super(id, clock, baseTimeUnit, distributionStatisticConfig, supportsAggregablePercentiles); + final String baseName = meterId.getName(); + + MeterFactory.gauge( + meterId.copyTo(baseName + "_active_count", MeterId.MeterType.GAUGE), () -> (double) activeTasks()).build(); + MeterFactory.gauge( + meterId.copyTo(baseName + "_duration_sum", MeterId.MeterType.GAUGE), () -> duration(TimeUnit.MILLISECONDS)).build(); + MeterFactory.gauge( + meterId.copyTo(baseName + "_max", MeterId.MeterType.GAUGE), () -> max(TimeUnit.MILLISECONDS)).build(); + } + +} diff --git a/apm-application-toolkit/apm-toolkit-micrometer-registry/src/main/java/org/apache/skywalking/apm/meter/micrometer/SkywalkingMeterRegistry.java b/apm-application-toolkit/apm-toolkit-micrometer-registry/src/main/java/org/apache/skywalking/apm/meter/micrometer/SkywalkingMeterRegistry.java new file mode 100644 index 0000000000..9b2d3230e1 --- /dev/null +++ b/apm-application-toolkit/apm-toolkit-micrometer-registry/src/main/java/org/apache/skywalking/apm/meter/micrometer/SkywalkingMeterRegistry.java @@ -0,0 +1,187 @@ +/* + * 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.apm.meter.micrometer; + +import io.micrometer.core.instrument.Clock; +import io.micrometer.core.instrument.DistributionSummary; +import io.micrometer.core.instrument.FunctionCounter; +import io.micrometer.core.instrument.FunctionTimer; +import io.micrometer.core.instrument.LongTaskTimer; +import io.micrometer.core.instrument.Measurement; +import io.micrometer.core.instrument.Meter; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.Statistic; +import io.micrometer.core.instrument.Timer; +import io.micrometer.core.instrument.config.NamingConvention; +import io.micrometer.core.instrument.cumulative.CumulativeFunctionCounter; +import io.micrometer.core.instrument.cumulative.CumulativeFunctionTimer; +import io.micrometer.core.instrument.distribution.DistributionStatisticConfig; +import io.micrometer.core.instrument.distribution.pause.PauseDetector; +import io.micrometer.core.instrument.internal.DefaultGauge; +import io.micrometer.core.instrument.internal.DefaultMeter; +import org.apache.skywalking.apm.toolkit.meter.MeterFactory; +import org.apache.skywalking.apm.toolkit.meter.impl.MeterCenter; +import org.apache.skywalking.apm.toolkit.meter.MeterId; + +import java.util.concurrent.TimeUnit; +import java.util.function.ToDoubleFunction; +import java.util.function.ToLongFunction; + +/** + * Skywalking adapt the micrometer registry. + */ +public class SkywalkingMeterRegistry extends MeterRegistry { + + private final SkywalkingConfig config; + + public SkywalkingMeterRegistry() { + this(SkywalkingConfig.DEFAULT, Clock.SYSTEM); + } + + public SkywalkingMeterRegistry(SkywalkingConfig config) { + this(config, Clock.SYSTEM); + } + + public SkywalkingMeterRegistry(Clock clock) { + this(SkywalkingConfig.DEFAULT, clock); + } + + public SkywalkingMeterRegistry(SkywalkingConfig config, Clock clock) { + super(clock); + this.config = config; + config().namingConvention(NamingConvention.snakeCase); + config().onMeterRemoved(this::onMeterRemoved); + } + + @Override + protected io.micrometer.core.instrument.Gauge newGauge(Meter.Id id, T obj, ToDoubleFunction valueFunction) { + final MeterId meterId = convertId(id); + MeterFactory.gauge(meterId, () -> valueFunction.applyAsDouble(obj)).build(); + return new DefaultGauge<>(id, obj, valueFunction); + } + + @Override + protected io.micrometer.core.instrument.Counter newCounter(Meter.Id id) { + final MeterId meterId = convertId(id); + return new SkywalkingCounter(id, MeterBuilder.buildCounter(meterId, config)); + } + + @Override + protected LongTaskTimer newLongTaskTimer(Meter.Id id, DistributionStatisticConfig distributionStatisticConfig) { + final MeterId meterId = convertId(id); + return new SkywalkingLongTaskTimer(id, meterId, clock, TimeUnit.MILLISECONDS, distributionStatisticConfig, true); + } + + @Override + protected Timer newTimer(Meter.Id id, DistributionStatisticConfig distributionStatisticConfig, PauseDetector pauseDetector) { + final MeterId meterId = convertId(id); + return new SkywalkingTimer(id, meterId, config, clock, distributionStatisticConfig, pauseDetector, TimeUnit.MILLISECONDS, true); + } + + @Override + protected DistributionSummary newDistributionSummary(Meter.Id id, DistributionStatisticConfig distributionStatisticConfig, double scale) { + final MeterId meterId = convertId(id); + return new SkywalkingDistributionSummary(id, meterId, config, clock, distributionStatisticConfig, scale, true); + } + + @Override + protected Meter newMeter(Meter.Id id, Meter.Type type, Iterable measurements) { + final MeterId meterId = convertId(id); + final String baseName = meterId.getName(); + + measurements.forEach(m -> { + String meterName = baseName; + boolean isCounter = false; + switch (m.getStatistic()) { + case TOTAL: + case TOTAL_TIME: + meterName = baseName + "_sum"; + isCounter = true; + break; + case COUNT: + isCounter = true; + break; + case MAX: + meterName = baseName + "_max"; + break; + case ACTIVE_TASKS: + meterName = baseName + "_active_count"; + break; + case DURATION: + meterName = baseName + "_duration_sum"; + break; + } + + if (isCounter) { + new SkywalkingCustomCounter.Builder(meterId.copyTo(meterName, MeterId.MeterType.COUNTER), m, config).build(); + } else { + MeterFactory.gauge(meterId.copyTo(meterName, MeterId.MeterType.GAUGE), () -> m.getValue()).build(); + } + }); + + return new DefaultMeter(id, type, measurements); + } + + @Override + protected FunctionTimer newFunctionTimer(Meter.Id id, T obj, ToLongFunction countFunction, ToDoubleFunction totalTimeFunction, TimeUnit totalTimeFunctionUnit) { + final MeterId meterId = convertId(id); + FunctionTimer ft = new CumulativeFunctionTimer<>(id, obj, countFunction, totalTimeFunction, totalTimeFunctionUnit, getBaseTimeUnit()); + final String baseName = meterId.getName(); + + MeterFactory.gauge( + meterId.copyTo(baseName + "_count", MeterId.MeterType.GAUGE), () -> ft.count()).build(); + MeterFactory.gauge( + meterId.copyTo(baseName + "_sum", MeterId.MeterType.GAUGE), () -> ft.totalTime(TimeUnit.MILLISECONDS)).build(); + return ft; + } + + @Override + protected FunctionCounter newFunctionCounter(Meter.Id id, T obj, ToDoubleFunction countFunction) { + final MeterId meterId = convertId(id); + FunctionCounter fc = new CumulativeFunctionCounter<>(id, obj, countFunction); + + new SkywalkingCustomCounter.Builder(meterId, new Measurement(() -> countFunction.applyAsDouble(obj), Statistic.COUNT), config).build(); + return fc; + } + + @Override + protected TimeUnit getBaseTimeUnit() { + return TimeUnit.MILLISECONDS; + } + + @Override + protected DistributionStatisticConfig defaultHistogramConfig() { + return DistributionStatisticConfig.DEFAULT; + } + + /** + * Notify on the meter has been removed + */ + private void onMeterRemoved(Meter meter) { + final MeterId meterId = convertId(meter.getId()); + MeterCenter.removeMeter(meterId); + } + + /** + * Convert the micrometer meter id to skywalking meter id + */ + private MeterId convertId(Meter.Id id) { + return MeterBuilder.convertId(id, getConventionName(id)); + } +} diff --git a/apm-application-toolkit/apm-toolkit-micrometer-registry/src/main/java/org/apache/skywalking/apm/meter/micrometer/SkywalkingTimer.java b/apm-application-toolkit/apm-toolkit-micrometer-registry/src/main/java/org/apache/skywalking/apm/meter/micrometer/SkywalkingTimer.java new file mode 100644 index 0000000000..9291a42473 --- /dev/null +++ b/apm-application-toolkit/apm-toolkit-micrometer-registry/src/main/java/org/apache/skywalking/apm/meter/micrometer/SkywalkingTimer.java @@ -0,0 +1,99 @@ +/* + * 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.apm.meter.micrometer; + +import io.micrometer.core.instrument.AbstractTimer; +import io.micrometer.core.instrument.Clock; +import io.micrometer.core.instrument.distribution.DistributionStatisticConfig; +import io.micrometer.core.instrument.distribution.pause.PauseDetector; +import org.apache.skywalking.apm.toolkit.meter.Counter; +import org.apache.skywalking.apm.toolkit.meter.Gauge; +import org.apache.skywalking.apm.toolkit.meter.Histogram; +import org.apache.skywalking.apm.toolkit.meter.MeterFactory; +import org.apache.skywalking.apm.toolkit.meter.MeterId; + +import java.util.Optional; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.DoubleAccumulator; + +public class SkywalkingTimer extends AbstractTimer { + + /** + * Execute finished count + */ + private final Counter counter; + + /** + * Total execute finished duration + */ + private final Counter sum; + + /** + * Max duration of execute finished time + */ + private final Gauge max; + private final DoubleAccumulator maxAdder; + + /** + * Histogram of execute finished duration + */ + private final Optional histogram; + + protected SkywalkingTimer(Id id, MeterId meterId, SkywalkingConfig config, Clock clock, + DistributionStatisticConfig distributionStatisticConfig, PauseDetector pauseDetector, + TimeUnit baseTimeUnit, boolean supportsAggregablePercentiles) { + super(id, clock, distributionStatisticConfig, pauseDetector, baseTimeUnit, supportsAggregablePercentiles); + + // meter base name + String baseName = meterId.getName(); + + this.counter = MeterBuilder.buildCounter(meterId.copyTo(baseName + "_count", MeterId.MeterType.COUNTER), config); + this.sum = MeterBuilder.buildCounter(meterId.copyTo(baseName + "_sum", MeterId.MeterType.COUNTER), config); + this.maxAdder = new DoubleAccumulator((a, b) -> a > b ? a : b, 0.000); + this.max = MeterFactory.gauge(meterId.copyTo(baseName + "_max", MeterId.MeterType.GAUGE), + () -> maxAdder.doubleValue()).build(); + + this.histogram = MeterBuilder.buildHistogram(meterId, supportsAggregablePercentiles, distributionStatisticConfig, true); + } + + @Override + protected void recordNonNegative(long amount, TimeUnit unit) { + counter.increment(1d); + final long amountToMillisecond = TimeUnit.MILLISECONDS.convert(amount, unit); + sum.increment(amountToMillisecond); + maxAdder.accumulate(amountToMillisecond); + + histogram.ifPresent(h -> h.addValue(amountToMillisecond)); + } + + @Override + public long count() { + return (long) counter.get(); + } + + @Override + public double totalTime(TimeUnit unit) { + return unit.convert((long) sum.get(), TimeUnit.MILLISECONDS); + } + + @Override + public double max(TimeUnit unit) { + return unit.convert((long) max.get(), TimeUnit.MILLISECONDS); + } +} diff --git a/apm-application-toolkit/apm-toolkit-micrometer-registry/src/test/java/org/apache/skywalking/apm/meter/micrometer/MeterBuilderTest.java b/apm-application-toolkit/apm-toolkit-micrometer-registry/src/test/java/org/apache/skywalking/apm/meter/micrometer/MeterBuilderTest.java new file mode 100644 index 0000000000..144685d404 --- /dev/null +++ b/apm-application-toolkit/apm-toolkit-micrometer-registry/src/test/java/org/apache/skywalking/apm/meter/micrometer/MeterBuilderTest.java @@ -0,0 +1,92 @@ +/* + * 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.apm.meter.micrometer; + +import io.micrometer.core.instrument.Meter; +import io.micrometer.core.instrument.Tags; +import io.micrometer.core.instrument.distribution.DistributionStatisticConfig; +import org.apache.skywalking.apm.toolkit.meter.Histogram; +import org.apache.skywalking.apm.toolkit.meter.MeterId; +import org.junit.Assert; +import org.junit.Test; + +import java.time.Duration; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +public class MeterBuilderTest { + + @Test + public void testBuildHistogram() { + final MeterId meterId = new MeterId("test", MeterId.MeterType.COUNTER, + Arrays.asList(new MeterId.Tag("k1", "v1"))); + + // Build a new distribution config + final DistributionStatisticConfig statisticConfig = DistributionStatisticConfig.builder() + .serviceLevelObjectives(Duration.ofMillis(1).toNanos(), Duration.ofMillis(5).toNanos(), Duration.ofMillis(10).toNanos()) + .minimumExpectedValue(0d).build(); + + // Check buckets + final Optional histogramOptional = MeterBuilder.buildHistogram(meterId, true, statisticConfig, true); + final Histogram histogram = histogramOptional.orElse(null); + Assert.assertNotNull(histogram); + final Histogram.Bucket[] buckets = histogram.getBuckets(); + Assert.assertEquals(4, buckets.length); + Assert.assertEquals(0d, buckets[0].getBucket(), 0.0); + Assert.assertEquals(1d, buckets[1].getBucket(), 0.0); + Assert.assertEquals(5d, buckets[2].getBucket(), 0.0); + Assert.assertEquals(10d, buckets[3].getBucket(), 0.0); + + // Check meter id + Assert.assertEquals("test_histogram", histogram.getMeterId().getName()); + Assert.assertEquals(MeterId.MeterType.HISTOGRAM, histogram.getMeterId().getType()); + Assert.assertEquals(Arrays.asList(new MeterId.Tag("k1", "v1")), histogram.getMeterId().getTags()); + + // Don't need the histogram + Assert.assertNull(MeterBuilder.buildHistogram(meterId, true, DistributionStatisticConfig.DEFAULT, true).orElse(null)); + } + + @Test + public void testConvertId() { + final List meterTags = Arrays.asList(new MeterId.Tag("k1", "v1")); + + // Counter type check + final Meter.Id counterId = new Meter.Id("test", Tags.of("k1", "v1"), null, "test", Meter.Type.COUNTER); + assertId(MeterBuilder.convertId(counterId, "test"), "test", MeterId.MeterType.COUNTER, meterTags); + + // Gauge type check + final Meter.Id gaugeId = new Meter.Id("test", Tags.of("k1", "v1"), null, "test", Meter.Type.GAUGE); + assertId(MeterBuilder.convertId(gaugeId, "test"), "test", MeterId.MeterType.GAUGE, meterTags); + + // Histogram type check + final Meter.Id otherId = new Meter.Id("test", Tags.of("k1", "v1"), null, "test", Meter.Type.DISTRIBUTION_SUMMARY); + assertId(MeterBuilder.convertId(otherId, "test"), "test", MeterId.MeterType.HISTOGRAM, meterTags); + } + + /** + * Assert the meter id + */ + private void assertId(MeterId meterId, String name, MeterId.MeterType type, List tags) { + Assert.assertEquals(name, meterId.getName()); + Assert.assertEquals(type, meterId.getType()); + Assert.assertEquals(tags, meterId.getTags()); + } + +} diff --git a/apm-application-toolkit/apm-toolkit-micrometer-registry/src/test/java/org/apache/skywalking/apm/meter/micrometer/SkywalkingCounterTest.java b/apm-application-toolkit/apm-toolkit-micrometer-registry/src/test/java/org/apache/skywalking/apm/meter/micrometer/SkywalkingCounterTest.java new file mode 100644 index 0000000000..536bd2a728 --- /dev/null +++ b/apm-application-toolkit/apm-toolkit-micrometer-registry/src/test/java/org/apache/skywalking/apm/meter/micrometer/SkywalkingCounterTest.java @@ -0,0 +1,74 @@ +/* + * 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.apm.meter.micrometer; + +import io.micrometer.core.instrument.Counter; +import org.apache.skywalking.apm.toolkit.meter.MeterId; +import org.apache.skywalking.apm.toolkit.meter.impl.CounterImpl; +import org.junit.Assert; +import org.junit.Test; +import org.powermock.reflect.Whitebox; + +import java.util.Arrays; +import java.util.List; + +public class SkywalkingCounterTest extends SkywalkingMeterBaseTest { + + @Test + public void testCounter() { + // Creating a simplify micrometer counter + final SkywalkingMeterRegistry registry = new SkywalkingMeterRegistry(); + Counter counter = registry.counter("test_counter", "skywalking", "test"); + + // Check Skywalking counter type + Assert.assertTrue(counter instanceof SkywalkingCounter); + final SkywalkingCounter skywalkingCounter = (SkywalkingCounter) counter; + final org.apache.skywalking.apm.toolkit.meter.Counter realCounter = + Whitebox.getInternalState(skywalkingCounter, "counter"); + + final List tags = Arrays.asList(new MeterId.Tag("skywalking", "test")); + + // Simplify increment + skywalkingCounter.increment(1d); + assertCounter(realCounter, "test_counter", tags, 1); + Assert.assertEquals(1d, skywalkingCounter.count(), 0.0); + + // Multiple increment + skywalkingCounter.increment(2d); + skywalkingCounter.increment(3d); + assertCounter(realCounter, "test_counter", tags, 6); + Assert.assertEquals(6d, skywalkingCounter.count(), 0.0); + } + + @Test + public void testRateCounter() { + final SkywalkingMeterRegistry registry = new SkywalkingMeterRegistry(new SkywalkingConfig(Arrays.asList("test_rate_counter"))); + final Counter counter = registry.counter("test_rate_counter", "skywalking", "test"); + + // Check Skywalking counter type + Assert.assertTrue(counter instanceof SkywalkingCounter); + final SkywalkingCounter skywalkingCounter = (SkywalkingCounter) counter; + final CounterImpl realCounter = + Whitebox.getInternalState(skywalkingCounter, "counter"); + + // check mode + final org.apache.skywalking.apm.toolkit.meter.Counter.Mode counterMode = Whitebox.getInternalState(realCounter, "mode"); + Assert.assertEquals(org.apache.skywalking.apm.toolkit.meter.Counter.Mode.RATE, counterMode); + } +} diff --git a/apm-application-toolkit/apm-toolkit-micrometer-registry/src/test/java/org/apache/skywalking/apm/meter/micrometer/SkywalkingCustomCounterTest.java b/apm-application-toolkit/apm-toolkit-micrometer-registry/src/test/java/org/apache/skywalking/apm/meter/micrometer/SkywalkingCustomCounterTest.java new file mode 100644 index 0000000000..ff88024b09 --- /dev/null +++ b/apm-application-toolkit/apm-toolkit-micrometer-registry/src/test/java/org/apache/skywalking/apm/meter/micrometer/SkywalkingCustomCounterTest.java @@ -0,0 +1,43 @@ +/* + * 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.apm.meter.micrometer; + +import io.micrometer.core.instrument.Measurement; +import io.micrometer.core.instrument.Statistic; +import org.apache.skywalking.apm.toolkit.meter.Counter; +import org.apache.skywalking.apm.toolkit.meter.MeterId; +import org.junit.Test; + +import java.util.Arrays; +import java.util.List; + +public class SkywalkingCustomCounterTest extends SkywalkingMeterBaseTest { + + @Test + public void testBuild() { + // Creating a custom measurement + Measurement measurement = new Measurement(() -> 1d, Statistic.COUNT); + final List tags = Arrays.asList(new MeterId.Tag("skywalking", "custom_counter")); + final MeterId meterId = new MeterId("test_custom_conter", MeterId.MeterType.COUNTER, tags); + final Counter counter = new SkywalkingCustomCounter.Builder(meterId, measurement, SkywalkingConfig.DEFAULT).build(); + + // Check is counter meter id and value + assertCounter(counter, "test_custom_conter", tags, 1d); + } +} diff --git a/apm-application-toolkit/apm-toolkit-micrometer-registry/src/test/java/org/apache/skywalking/apm/meter/micrometer/SkywalkingDistributionSummaryTest.java b/apm-application-toolkit/apm-toolkit-micrometer-registry/src/test/java/org/apache/skywalking/apm/meter/micrometer/SkywalkingDistributionSummaryTest.java new file mode 100644 index 0000000000..693dacb587 --- /dev/null +++ b/apm-application-toolkit/apm-toolkit-micrometer-registry/src/test/java/org/apache/skywalking/apm/meter/micrometer/SkywalkingDistributionSummaryTest.java @@ -0,0 +1,88 @@ +/* + * 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.apm.meter.micrometer; + +import io.micrometer.core.instrument.DistributionSummary; +import org.apache.skywalking.apm.toolkit.meter.MeterId; +import org.junit.Assert; +import org.junit.Test; +import org.powermock.reflect.Whitebox; + +import java.util.Arrays; +import java.util.List; + +public class SkywalkingDistributionSummaryTest extends SkywalkingMeterBaseTest { + + @Test + public void testSimple() { + // Creating a simplify distribution summary + final SkywalkingMeterRegistry registry = new SkywalkingMeterRegistry(); + final DistributionSummary summary = registry.summary("test_simple_distribution_summary", "skywalking", "test"); + + // Check Skywalking type + Assert.assertTrue(summary instanceof SkywalkingDistributionSummary); + final List tags = Arrays.asList(new MeterId.Tag("skywalking", "test")); + + // Multiple record data + summary.record(10d); + summary.record(13d); + summary.record(2d); + + // Check micrometer data + Assert.assertEquals(3, summary.count()); + Assert.assertEquals(25d, summary.totalAmount(), 0.0); + Assert.assertEquals(13d, summary.max(), 0.0); + + // Check Skywalking data + assertCounter(Whitebox.getInternalState(summary, "counter"), "test_simple_distribution_summary_count", tags, 3d); + assertCounter(Whitebox.getInternalState(summary, "sum"), "test_simple_distribution_summary_sum", tags, 25d); + assertGauge(Whitebox.getInternalState(summary, "max"), "test_simple_distribution_summary_max", tags, 13d); + assertHistogramNull(Whitebox.getInternalState(summary, "histogram")); + } + + @Test + public void testComplex() { + // Creating a support histogram distribution summary + final SkywalkingMeterRegistry registry = new SkywalkingMeterRegistry(); + final DistributionSummary summary = DistributionSummary.builder("test_complex_distribution_summary") + .tags("skywalking", "test") + .publishPercentiles(0.5, 0.95) + .serviceLevelObjectives(10, 20) + .minimumExpectedValue(1d) + .register(registry); + + final List tags = Arrays.asList(new MeterId.Tag("skywalking", "test")); + + // Multiple record data + summary.record(10d); + summary.record(13d); + summary.record(2d); + + // Check micrometer data + Assert.assertEquals(3, summary.count()); + Assert.assertEquals(25d, summary.totalAmount(), 0.0); + Assert.assertEquals(13d, summary.max(), 0.0); + + // Check Skywalking data + assertCounter(Whitebox.getInternalState(summary, "counter"), "test_complex_distribution_summary_count", tags, 3d); + assertCounter(Whitebox.getInternalState(summary, "sum"), "test_complex_distribution_summary_sum", tags, 25d); + assertGauge(Whitebox.getInternalState(summary, "max"), "test_complex_distribution_summary_max", tags, 13d); + assertHistogram(Whitebox.getInternalState(summary, "histogram"), "test_complex_distribution_summary_histogram", tags, 1, 1, 10, 2, 20, 0); + } +} diff --git a/apm-application-toolkit/apm-toolkit-micrometer-registry/src/test/java/org/apache/skywalking/apm/meter/micrometer/SkywalkingLongTaskTimerTest.java b/apm-application-toolkit/apm-toolkit-micrometer-registry/src/test/java/org/apache/skywalking/apm/meter/micrometer/SkywalkingLongTaskTimerTest.java new file mode 100644 index 0000000000..35148a5422 --- /dev/null +++ b/apm-application-toolkit/apm-toolkit-micrometer-registry/src/test/java/org/apache/skywalking/apm/meter/micrometer/SkywalkingLongTaskTimerTest.java @@ -0,0 +1,89 @@ +/* + * 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.apm.meter.micrometer; + +import io.micrometer.core.instrument.LongTaskTimer; +import org.apache.skywalking.apm.toolkit.meter.Gauge; +import org.apache.skywalking.apm.toolkit.meter.impl.AbstractMeter; +import org.apache.skywalking.apm.toolkit.meter.impl.MeterCenter; +import org.apache.skywalking.apm.toolkit.meter.MeterId; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.powermock.reflect.Whitebox; + +import java.util.Arrays; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +public class SkywalkingLongTaskTimerTest extends SkywalkingMeterBaseTest { + + private Map meterMap; + + @Before + public void setup() { + // Need to clear all of the meter, long task timer has some meter not field field reference + meterMap = Whitebox.getInternalState(MeterCenter.class, "METER_MAP"); + meterMap.clear(); + } + + @Test + public void testSimple() throws InterruptedException { + // Creating a simplify long task timer + final SkywalkingMeterRegistry registry = new SkywalkingMeterRegistry(); + final LongTaskTimer longTaskTimer = registry.more().longTaskTimer("test_simple_long_task_timer", "skywalking", "test"); + + // Adding tasks + addLongTask(longTaskTimer, 450); + addLongTask(longTaskTimer, 20); + + // Make sure the second task has finished + TimeUnit.MILLISECONDS.sleep(200); + + // Check Skywalking type + Assert.assertTrue(longTaskTimer instanceof SkywalkingLongTaskTimer); + final SkywalkingLongTaskTimer timer = (SkywalkingLongTaskTimer) longTaskTimer; + + // Check Original data + Assert.assertEquals(1, timer.activeTasks()); + Assert.assertTrue(timer.duration(TimeUnit.MILLISECONDS) > 0); + Assert.assertTrue(timer.max(TimeUnit.MILLISECONDS) > 0); + + // Check Skywalking data + assertGauge((Gauge) meterMap.values().stream().filter(m -> m.getName().endsWith("_active_count")).findFirst().orElse(null), + "test_simple_long_task_timer_active_count", Arrays.asList(new MeterId.Tag("skywalking", "test")), 1); + assertGauge((Gauge) meterMap.values().stream().filter(m -> m.getName().endsWith("_duration_sum")).findFirst().orElse(null), + "test_simple_long_task_timer_duration_sum", Arrays.asList(new MeterId.Tag("skywalking", "test")), 0, true); + assertGauge((Gauge) meterMap.values().stream().filter(m -> m.getName().endsWith("_max")).findFirst().orElse(null), + "test_simple_long_task_timer_max", Arrays.asList(new MeterId.Tag("skywalking", "test")), 0, true); + } + + // Add long time task + private void addLongTask(LongTaskTimer longTaskTimer, int sleepMills) { + new Thread(() -> { + longTaskTimer.record(() -> { + try { + TimeUnit.MILLISECONDS.sleep(sleepMills); + } catch (InterruptedException e) { + } + }); + }).start(); + } + +} diff --git a/apm-application-toolkit/apm-toolkit-micrometer-registry/src/test/java/org/apache/skywalking/apm/meter/micrometer/SkywalkingMeterBaseTest.java b/apm-application-toolkit/apm-toolkit-micrometer-registry/src/test/java/org/apache/skywalking/apm/meter/micrometer/SkywalkingMeterBaseTest.java new file mode 100644 index 0000000000..454b9238f8 --- /dev/null +++ b/apm-application-toolkit/apm-toolkit-micrometer-registry/src/test/java/org/apache/skywalking/apm/meter/micrometer/SkywalkingMeterBaseTest.java @@ -0,0 +1,91 @@ +/* + * 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.apm.meter.micrometer; + +import org.apache.skywalking.apm.toolkit.meter.Counter; +import org.apache.skywalking.apm.toolkit.meter.Gauge; +import org.apache.skywalking.apm.toolkit.meter.Histogram; +import org.apache.skywalking.apm.toolkit.meter.MeterId; +import org.junit.Assert; + +import java.util.List; +import java.util.Optional; + +public class SkywalkingMeterBaseTest { + + /** + * Check counter data + */ + public void assertCounter(Counter counter, String name, List tags, double count) { + Assert.assertEquals(name, counter.getMeterId().getName()); + Assert.assertEquals(tags, counter.getMeterId().getTags()); + Assert.assertEquals(MeterId.MeterType.COUNTER, counter.getMeterId().getType()); + + Assert.assertEquals(count, counter.get(), 0.0); + } + + /** + * Check gauge data, and value must be same + */ + public void assertGauge(Gauge gauge, String name, List tags, double value) { + assertGauge(gauge, name, tags, value, false); + } + + /** + * Check gauge data, and value could greater than provide value + */ + public void assertGauge(Gauge gauge, String name, List tags, double value, boolean greaterThanValueMode) { + Assert.assertEquals(name, gauge.getMeterId().getName()); + Assert.assertEquals(tags, gauge.getMeterId().getTags()); + Assert.assertEquals(MeterId.MeterType.GAUGE, gauge.getMeterId().getType()); + + if (greaterThanValueMode) { + Assert.assertTrue(gauge.get() > value); + } else { + Assert.assertEquals(value, gauge.get(), 0.0); + } + } + + /** + * Check not have histogram + */ + public void assertHistogramNull(Optional histogramOptional) { + Assert.assertNull(histogramOptional.orElse(null)); + } + + /** + * Check histogram cannot be null and data correct + * @param bucketsAndCount bucket and value array + */ + public void assertHistogram(Optional histogramOptional, String name, List tags, double... bucketsAndCount) { + final Histogram histogram = histogramOptional.orElse(null); + Assert.assertNotNull(histogram); + Assert.assertEquals(name, histogram.getMeterId().getName()); + Assert.assertEquals(tags, histogram.getMeterId().getTags()); + Assert.assertEquals(MeterId.MeterType.HISTOGRAM, histogram.getMeterId().getType()); + + final Histogram.Bucket[] buckets = histogram.getBuckets(); + Assert.assertEquals(bucketsAndCount.length / 2, buckets.length); + for (int i = 0; i < buckets.length; i++) { + Assert.assertEquals(bucketsAndCount[i * 2], buckets[i].getBucket(), 0.0); + Assert.assertEquals((long) bucketsAndCount[i * 2 + 1], buckets[i].getCount()); + } + } + +} diff --git a/apm-application-toolkit/apm-toolkit-micrometer-registry/src/test/java/org/apache/skywalking/apm/meter/micrometer/SkywalkingMeterRegistryTest.java b/apm-application-toolkit/apm-toolkit-micrometer-registry/src/test/java/org/apache/skywalking/apm/meter/micrometer/SkywalkingMeterRegistryTest.java new file mode 100644 index 0000000000..8604b31859 --- /dev/null +++ b/apm-application-toolkit/apm-toolkit-micrometer-registry/src/test/java/org/apache/skywalking/apm/meter/micrometer/SkywalkingMeterRegistryTest.java @@ -0,0 +1,241 @@ +/* + * 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.apm.meter.micrometer; + +import io.micrometer.core.instrument.Measurement; +import io.micrometer.core.instrument.Meter; +import io.micrometer.core.instrument.Statistic; +import io.micrometer.core.instrument.Tags; +import org.apache.skywalking.apm.toolkit.meter.BaseMeter; +import org.apache.skywalking.apm.toolkit.meter.Counter; +import org.apache.skywalking.apm.toolkit.meter.Gauge; +import org.apache.skywalking.apm.toolkit.meter.impl.MeterCenter; +import org.apache.skywalking.apm.toolkit.meter.MeterId; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.powermock.reflect.Whitebox; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +/** + * Only test in build-in meters + */ +public class SkywalkingMeterRegistryTest extends SkywalkingMeterBaseTest { + + private Map meterMap; + + @Before + public void setup() { + // Make sure meters are clear + meterMap = Whitebox.getInternalState(MeterCenter.class, "METER_MAP"); + meterMap.clear(); + } + + @After + public void cleanup() { + // Clear meters after finish each test case + meterMap.clear(); + } + + @Test + public void testGauge() { + final SkywalkingMeterRegistry registry = new SkywalkingMeterRegistry(); + final GaugeTestBean gaugeTestBean = new GaugeTestBean(1d); + registry.gauge("test_counter", gaugeTestBean, GaugeTestBean::getCount); + + // Check meter and count + Assert.assertEquals(1, meterMap.size()); + final BaseMeter meter = meterMap.values().iterator().next(); + Assert.assertTrue(meter instanceof Gauge); + final Gauge gauge = (Gauge) meter; + + Assert.assertEquals(1d, gauge.get(), 0.0); + } + + @Test + public void testFunctionTimer() { + final SkywalkingMeterRegistry registry = new SkywalkingMeterRegistry(); + final FunctionTimerBean task = new FunctionTimerBean(1, 200); + registry.more().timer("test_function_timer", Tags.of("skywalking", "test"), task, + FunctionTimerBean::getCount, FunctionTimerBean::getTotalTime, TimeUnit.MILLISECONDS); + final List tags = Arrays.asList(new MeterId.Tag("skywalking", "test")); + + // Check is has appoint meter + Assert.assertEquals(2, meterMap.size()); + Gauge countGauge = null; + Gauge sumGauge = null; + for (BaseMeter meter : meterMap.values()) { + if (meter.getName().endsWith("count")) { + countGauge = (Gauge) meter; + } else if (meter.getName().endsWith("sum")) { + sumGauge = (Gauge) meter; + } + } + + // Check data + assertGauge(countGauge, "test_function_timer_count", tags, 1); + assertGauge(sumGauge, "test_function_timer_sum", tags, 200); + } + + @Test + public void testFunctionCounter() { + final SkywalkingMeterRegistry registry = new SkywalkingMeterRegistry(); + final FunctionTimerBean task = new FunctionTimerBean(1, 200); + registry.more().counter("test_function_counter", Tags.of("skywalking", "test"), task, + FunctionTimerBean::getCount); + final List tags = Arrays.asList(new MeterId.Tag("skywalking", "test")); + + // Check meter and count + Assert.assertEquals(1, meterMap.size()); + Counter countGauge = (Counter) meterMap.values().iterator().next(); + + // Check data + assertCounter(countGauge, "test_function_counter", tags, 1); + } + + @Test + public void testNewMeterSum() { + // sum + testNewMeter("test_meter", Meter.Type.GAUGE, Statistic.TOTAL, data -> { + assertCounter((Counter) data.getMeter(), + "test_meter_sum", data.getTags(), 1d); + }); + + // count + testNewMeter("test_meter", Meter.Type.COUNTER, Statistic.COUNT, data -> { + assertCounter((Counter) data.getMeter(), + "test_meter", data.getTags(), 1d); + }); + + // max + testNewMeter("test_meter", Meter.Type.GAUGE, Statistic.MAX, data -> { + assertGauge((Gauge) data.getMeter(), + "test_meter_max", data.getTags(), 1d); + }); + + // activeCount + testNewMeter("test_meter", Meter.Type.GAUGE, Statistic.ACTIVE_TASKS, data -> { + assertGauge((Gauge) data.getMeter(), + "test_meter_active_count", data.getTags(), 1d); + }); + + // durationSum + testNewMeter("test_meter", Meter.Type.GAUGE, Statistic.DURATION, data -> { + assertGauge((Gauge) data.getMeter(), + "test_meter_duration_sum", data.getTags(), 1d); + }); + + // others + testNewMeter("test_meter", Meter.Type.GAUGE, Statistic.VALUE, data -> { + assertGauge((Gauge) data.getMeter(), + "test_meter", data.getTags(), 1d); + }); + } + + /** + * Check custom measurement + */ + private void testNewMeter(String meterName, Meter.Type type, Statistic statistic, Consumer meterChecker) { + final SkywalkingMeterRegistry registry = new SkywalkingMeterRegistry(); + + // Create measurement + Meter.builder(meterName, type, Arrays.asList(new Measurement(() -> 1d, statistic))) + .tag("skywalking", "test") + .register(registry); + final List tags = Arrays.asList(new MeterId.Tag("skywalking", "test")); + Assert.assertEquals(1, meterMap.size()); + meterChecker.accept(new MeterData(meterMap.values().iterator().next(), tags)); + + // clear all data + cleanup(); + } + + @Test + public void testRemove() { + final SkywalkingMeterRegistry registry = new SkywalkingMeterRegistry(); + final io.micrometer.core.instrument.Counter counter = registry.counter("test_remove_counter"); + Assert.assertEquals(1, meterMap.size()); + + registry.remove(counter.getId()); + Assert.assertEquals(0, meterMap.size()); + } + + /** + * Working on {@link io.micrometer.core.instrument.Gauge} check + */ + private static class GaugeTestBean { + private final double count; + + public GaugeTestBean(double count) { + this.count = count; + } + + public double getCount() { + return count; + } + } + + /** + * Working on {@link io.micrometer.core.instrument.FunctionTimer} check + */ + private static class FunctionTimerBean { + private final long count; + private final double totalTime; + + public FunctionTimerBean(long count, double totalTime) { + this.count = count; + this.totalTime = totalTime; + } + + public long getCount() { + return count; + } + + public double getTotalTime() { + return totalTime; + } + } + + /** + * Working on custom {@link Measurement} check + */ + private static class MeterData { + private final BaseMeter meter; + private final List tags; + + public MeterData(BaseMeter meter, List tags) { + this.meter = meter; + this.tags = tags; + } + + public BaseMeter getMeter() { + return meter; + } + + public List getTags() { + return tags; + } + } +} diff --git a/apm-application-toolkit/apm-toolkit-micrometer-registry/src/test/java/org/apache/skywalking/apm/meter/micrometer/SkywalkingTimerTest.java b/apm-application-toolkit/apm-toolkit-micrometer-registry/src/test/java/org/apache/skywalking/apm/meter/micrometer/SkywalkingTimerTest.java new file mode 100644 index 0000000000..e4dedd2c7e --- /dev/null +++ b/apm-application-toolkit/apm-toolkit-micrometer-registry/src/test/java/org/apache/skywalking/apm/meter/micrometer/SkywalkingTimerTest.java @@ -0,0 +1,92 @@ +/* + * 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.apm.meter.micrometer; + +import io.micrometer.core.instrument.Timer; +import org.apache.skywalking.apm.toolkit.meter.MeterId; +import org.junit.Assert; +import org.junit.Test; +import org.powermock.reflect.Whitebox; + +import java.time.Duration; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; + +public class SkywalkingTimerTest extends SkywalkingMeterBaseTest { + + @Test + public void testSimpleTimer() { + // Creating a simplify timer + final SkywalkingMeterRegistry registry = new SkywalkingMeterRegistry(); + final Timer timer = registry.timer("test_simple_timer", "skywalking", "test"); + + // Check Skywalking type + Assert.assertTrue(timer instanceof SkywalkingTimer); + final List tags = Arrays.asList(new MeterId.Tag("skywalking", "test")); + + // Multiple record data + timer.record(10, TimeUnit.MILLISECONDS); + timer.record(20, TimeUnit.MILLISECONDS); + timer.record(3, TimeUnit.MILLISECONDS); + + // Check micrometer data + Assert.assertEquals(3, timer.count()); + Assert.assertEquals(33d, timer.totalTime(TimeUnit.MILLISECONDS), 0.0); + Assert.assertEquals(20d, timer.max(TimeUnit.MILLISECONDS), 0.0); + + // Check Skywalking data + assertCounter(Whitebox.getInternalState(timer, "counter"), "test_simple_timer_count", tags, 3d); + assertCounter(Whitebox.getInternalState(timer, "sum"), "test_simple_timer_sum", tags, 33d); + assertGauge(Whitebox.getInternalState(timer, "max"), "test_simple_timer_max", tags, 20d); + assertHistogramNull(Whitebox.getInternalState(timer, "histogram")); + } + + @Test + public void testBuilder() { + // Creating a support histogram timer + final SkywalkingMeterRegistry registry = new SkywalkingMeterRegistry(); + Timer timer = Timer.builder("test_complex_timer") + .tag("skywalking", "test") + .publishPercentiles(0.5, 0.95) // median and 95th percentile + .serviceLevelObjectives(Duration.ofMillis(10), Duration.ofMillis(20)) + .minimumExpectedValue(Duration.ofMillis(1)) + .register(registry); + + // Check Skywalking type + Assert.assertTrue(timer instanceof SkywalkingTimer); + final List tags = Arrays.asList(new MeterId.Tag("skywalking", "test")); + + // Multiple record data + timer.record(10, TimeUnit.MILLISECONDS); + timer.record(22, TimeUnit.MILLISECONDS); + timer.record(13, TimeUnit.MILLISECONDS); + + // Check micrometer data + Assert.assertEquals(3, timer.count()); + Assert.assertEquals(45d, timer.totalTime(TimeUnit.MILLISECONDS), 0.0); + Assert.assertEquals(22d, timer.max(TimeUnit.MILLISECONDS), 0.0); + + // Check Skywalking data + assertCounter(Whitebox.getInternalState(timer, "counter"), "test_complex_timer_count", tags, 3d); + assertCounter(Whitebox.getInternalState(timer, "sum"), "test_complex_timer_sum", tags, 45d); + assertGauge(Whitebox.getInternalState(timer, "max"), "test_complex_timer_max", tags, 22d); + assertHistogram(Whitebox.getInternalState(timer, "histogram"), "test_complex_timer_histogram", tags, 1, 0, 10, 2, 20, 1); + } +} diff --git a/apm-application-toolkit/pom.xml b/apm-application-toolkit/pom.xml index 4e06e7b849..8ba22bfe2a 100644 --- a/apm-application-toolkit/pom.xml +++ b/apm-application-toolkit/pom.xml @@ -36,5 +36,7 @@ apm-toolkit-logback-1.x apm-toolkit-opentracing apm-toolkit-trace + apm-toolkit-meter + apm-toolkit-micrometer-registry diff --git a/apm-protocol/apm-network/src/main/proto b/apm-protocol/apm-network/src/main/proto index 7e563891f3..24788cf418 160000 --- a/apm-protocol/apm-network/src/main/proto +++ b/apm-protocol/apm-network/src/main/proto @@ -1 +1 @@ -Subproject commit 7e563891f3cfaf5c19ad086434af93aaab996719 +Subproject commit 24788cf41807048dcbe8dba0958688aaaf630d73 diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/conf/Config.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/conf/Config.java index e6e4ffb91f..5f5542a3be 100755 --- a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/conf/Config.java +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/conf/Config.java @@ -170,6 +170,23 @@ public class Config { public static int SNAPSHOT_TRANSPORT_BUFFER_SIZE = 500; } + public static class Meter { + /** + * If true, skywalking agent will enable sending meters. Otherwise disable meter report. + */ + public static boolean ACTIVE = true; + + /** + * Report meters interval + */ + public static Integer REPORT_INTERVAL = 20; + + /** + * Max size of the meter count, using {@link org.apache.skywalking.apm.agent.core.meter.MeterId} as identity + */ + public static Integer MAX_METER_SIZE = 500; + } + public static class Jvm { /** * The buffer size of collected JVM info. diff --git a/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/meter/MeterId.java b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/meter/MeterId.java new file mode 100644 index 0000000000..fc33e2b098 --- /dev/null +++ b/apm-sniffer/apm-agent-core/src/main/java/org/apache/skywalking/apm/agent/core/meter/MeterId.java @@ -0,0 +1,84 @@ +/* + * 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.apm.agent.core.meter; + +import org.apache.skywalking.apm.network.language.agent.v3.Label; + +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * Identity the meter, including name, type and tags. + */ +public class MeterId { + + private final String name; + private final MeterType type; + private final List tags; + + // cache the gRPC label message + private List