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 000000000..48c0c7fe2
--- /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 000000000..d73ddff9b
--- /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 000000000..de7a2a0f5
--- /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 000000000..fb7cd7352
--- /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 000000000..fd2d2ae0c
--- /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 000000000..5869052e0
--- /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 000000000..60d261108
--- /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 000000000..34949ce32
--- /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 000000000..3c92aa224
--- /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 000000000..b3746b7b0
--- /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 000000000..3a7ee41b0
--- /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 000000000..0474398c4
--- /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 000000000..baa5d1f87
--- /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 000000000..e9d538b04
--- /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 000000000..511af95d4
--- /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 000000000..572c47866
--- /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 000000000..bc707d310
--- /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 000000000..89d0980ca
--- /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 000000000..58f19ad1f
--- /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 000000000..5d3c0a4d5
--- /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 000000000..bb63e4d06
--- /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 000000000..609af84b4
--- /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 000000000..1cfef191d
--- /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 000000000..a88700670
--- /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 000000000..0efde2f1a
--- /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 000000000..73a47cf89
--- /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 000000000..1ab33ff93
--- /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 000000000..9b2d3230e
--- /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 000000000..9291a4247
--- /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 000000000..144685d40
--- /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 000000000..536bd2a72
--- /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 000000000..ff88024b0
--- /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 000000000..693dacb58
--- /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 000000000..35148a542
--- /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 000000000..454b9238f
--- /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 000000000..8604b3185
--- /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 000000000..e4dedd2c7
--- /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 4e06e7b84..8ba22bfe2 100644
--- a/apm-application-toolkit/pom.xml
+++ b/apm-application-toolkit/pom.xml
@@ -36,5 +36,7 @@
apm-toolkit-logback-1.xapm-toolkit-opentracingapm-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 7e563891f..24788cf41 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 e6e4ffb91..5f5542a3b 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 000000000..fc33e2b09
--- /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