You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@servicecomb.apache.org by GitBox <gi...@apache.org> on 2018/03/15 11:08:47 UTC

[GitHub] liubao68 closed pull request #575: [SCB-369] Extract info from spectator measurement

liubao68 closed pull request #575: [SCB-369] Extract info from spectator measurement
URL: https://github.com/apache/incubator-servicecomb-java-chassis/pull/575
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

diff --git a/foundations/foundation-metrics/pom.xml b/foundations/foundation-metrics/pom.xml
index 96bd6f7a1..baa407cb7 100644
--- a/foundations/foundation-metrics/pom.xml
+++ b/foundations/foundation-metrics/pom.xml
@@ -35,6 +35,10 @@
       <groupId>com.netflix.archaius</groupId>
       <artifactId>archaius-core</artifactId>
     </dependency>
+    <dependency>
+      <groupId>com.netflix.spectator</groupId>
+      <artifactId>spectator-reg-servo</artifactId>
+    </dependency>
     <dependency>
       <groupId>org.slf4j</groupId>
       <artifactId>slf4j-log4j12</artifactId>
diff --git a/foundations/foundation-metrics/src/main/java/org/apache/servicecomb/foundation/metrics/publish/spectator/DefaultTagFinder.java b/foundations/foundation-metrics/src/main/java/org/apache/servicecomb/foundation/metrics/publish/spectator/DefaultTagFinder.java
new file mode 100644
index 000000000..74601f3b9
--- /dev/null
+++ b/foundations/foundation-metrics/src/main/java/org/apache/servicecomb/foundation/metrics/publish/spectator/DefaultTagFinder.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.servicecomb.foundation.metrics.publish.spectator;
+
+import com.netflix.spectator.api.Tag;
+
+public class DefaultTagFinder implements TagFinder {
+  private String tagKey;
+
+  public DefaultTagFinder(String tagKey) {
+    this.tagKey = tagKey;
+  }
+
+  @Override
+  public String getTagKey() {
+    return tagKey;
+  }
+
+  @Override
+  public Tag find(Iterable<Tag> tags) {
+    for (Tag tag : tags) {
+      if (tag.key().equals(tagKey)) {
+        return tag;
+      }
+    }
+
+    return null;
+  }
+}
diff --git a/foundations/foundation-metrics/src/main/java/org/apache/servicecomb/foundation/metrics/publish/spectator/MeasurementGroupConfig.java b/foundations/foundation-metrics/src/main/java/org/apache/servicecomb/foundation/metrics/publish/spectator/MeasurementGroupConfig.java
new file mode 100644
index 000000000..48075fc8a
--- /dev/null
+++ b/foundations/foundation-metrics/src/main/java/org/apache/servicecomb/foundation/metrics/publish/spectator/MeasurementGroupConfig.java
@@ -0,0 +1,48 @@
+/*
+ * 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.servicecomb.foundation.metrics.publish.spectator;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+public class MeasurementGroupConfig {
+  // key is measurement id name
+  private Map<String, List<TagFinder>> groups = new HashMap<>();
+
+  public MeasurementGroupConfig() {
+  }
+
+  public MeasurementGroupConfig(String idName, Object... tagNameOrFinders) {
+    addGroup(idName, tagNameOrFinders);
+  }
+
+  public void addGroup(String idName, Object... tagNameOrFinders) {
+    groups.put(idName,
+        Arrays
+            .asList(tagNameOrFinders)
+            .stream()
+            .map(r -> TagFinder.build(r))
+            .collect(Collectors.toList()));
+  }
+
+  public List<TagFinder> findTagFinders(String idName) {
+    return groups.get(idName);
+  }
+}
diff --git a/foundations/foundation-metrics/src/main/java/org/apache/servicecomb/foundation/metrics/publish/spectator/MeasurementNode.java b/foundations/foundation-metrics/src/main/java/org/apache/servicecomb/foundation/metrics/publish/spectator/MeasurementNode.java
new file mode 100644
index 000000000..2d821134d
--- /dev/null
+++ b/foundations/foundation-metrics/src/main/java/org/apache/servicecomb/foundation/metrics/publish/spectator/MeasurementNode.java
@@ -0,0 +1,85 @@
+/*
+ * 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.servicecomb.foundation.metrics.publish.spectator;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.netflix.spectator.api.Measurement;
+
+public class MeasurementNode {
+  private String name;
+
+  private List<Measurement> measurements = new ArrayList<>();
+
+  private Map<String, MeasurementNode> children;
+
+  public MeasurementNode(String name, Map<String, MeasurementNode> children) {
+    this.name = name;
+    this.children = children;
+  }
+
+  public String getName() {
+    return name;
+  }
+
+  public Map<String, MeasurementNode> getChildren() {
+    return children;
+  }
+
+  public MeasurementNode findChild(String childName) {
+    if (children == null) {
+      return null;
+    }
+    return children.get(childName);
+  }
+
+  public MeasurementNode findChild(String... childNames) {
+    MeasurementNode node = this;
+    for (String childName : childNames) {
+      if (node == null) {
+        return null;
+      }
+
+      node = node.findChild(childName);
+    }
+    return node;
+  }
+
+  public MeasurementNode addChild(String childName, Measurement measurement) {
+    if (children == null) {
+      children = new HashMap<>();
+    }
+
+    MeasurementNode node = children.computeIfAbsent(childName, name -> {
+      return new MeasurementNode(name, null);
+    });
+    node.addMeasurement(measurement);
+
+    return node;
+  }
+
+  public List<Measurement> getMeasurements() {
+    return measurements;
+  }
+
+  public void addMeasurement(Measurement measurement) {
+    measurements.add(measurement);
+  }
+}
diff --git a/foundations/foundation-metrics/src/main/java/org/apache/servicecomb/foundation/metrics/publish/spectator/MeasurementTree.java b/foundations/foundation-metrics/src/main/java/org/apache/servicecomb/foundation/metrics/publish/spectator/MeasurementTree.java
new file mode 100644
index 000000000..056bba69c
--- /dev/null
+++ b/foundations/foundation-metrics/src/main/java/org/apache/servicecomb/foundation/metrics/publish/spectator/MeasurementTree.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.servicecomb.foundation.metrics.publish.spectator;
+
+import java.util.Iterator;
+import java.util.List;
+
+import com.netflix.spectator.api.Id;
+import com.netflix.spectator.api.Measurement;
+import com.netflix.spectator.api.Meter;
+import com.netflix.spectator.api.Tag;
+
+// like select * from meters group by ......
+// but output a tree not a table
+public class MeasurementTree extends MeasurementNode {
+  public MeasurementTree() {
+    super(null, null);
+  }
+
+  // groupConfig:
+  //   key: id name
+  //   value: id tag keys
+  // only id name exists in groupConfig will accept, others will be ignored
+  public void from(Iterator<Meter> meters, MeasurementGroupConfig groupConfig) {
+    meters.forEachRemaining(meter -> {
+      Iterable<Measurement> measurements = meter.measure();
+      from(measurements, groupConfig);
+    });
+  }
+
+  public void from(Iterable<Measurement> measurements, MeasurementGroupConfig groupConfig) {
+    for (Measurement measurement : measurements) {
+      Id id = measurement.id();
+      List<TagFinder> tagFinders = groupConfig.findTagFinders(id.name());
+      if (tagFinders == null) {
+        continue;
+      }
+
+      MeasurementNode node = addChild(id.name(), measurement);
+      for (TagFinder tagFinder : tagFinders) {
+        Tag tag = tagFinder.find(id.tags());
+        if (tag == null) {
+          throw new IllegalStateException(
+              String.format("tag key \"%s\" not exist in %s",
+                  tagFinder.getTagKey(),
+                  measurement));
+        }
+
+        node = node.addChild(tag.value(), measurement);
+      }
+    }
+  }
+}
diff --git a/foundations/foundation-metrics/src/main/java/org/apache/servicecomb/foundation/metrics/publish/spectator/TagFinder.java b/foundations/foundation-metrics/src/main/java/org/apache/servicecomb/foundation/metrics/publish/spectator/TagFinder.java
new file mode 100644
index 000000000..40c26bb6d
--- /dev/null
+++ b/foundations/foundation-metrics/src/main/java/org/apache/servicecomb/foundation/metrics/publish/spectator/TagFinder.java
@@ -0,0 +1,41 @@
+/*
+ * 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.servicecomb.foundation.metrics.publish.spectator;
+
+import com.netflix.spectator.api.Tag;
+
+public interface TagFinder {
+  static TagFinder build(Object obj) {
+    if (String.class.isInstance(obj)) {
+      return new DefaultTagFinder((String) obj);
+    }
+
+    if (TagFinder.class.isInstance(obj)) {
+      return (TagFinder) obj;
+    }
+
+    throw new IllegalArgumentException(
+        "only support String or TagFinder, but got " +
+            (obj == null ? "null" : obj.getClass().getName()));
+  }
+
+  String getTagKey();
+
+  // read target tag from tags
+  // return directly or do some change and then return
+  Tag find(Iterable<Tag> tags);
+}
diff --git a/foundations/foundation-metrics/src/test/java/org/apache/servicecomb/foundation/metrics/publish/spectator/TestDefaultTagFinder.java b/foundations/foundation-metrics/src/test/java/org/apache/servicecomb/foundation/metrics/publish/spectator/TestDefaultTagFinder.java
new file mode 100644
index 000000000..84a288a01
--- /dev/null
+++ b/foundations/foundation-metrics/src/test/java/org/apache/servicecomb/foundation/metrics/publish/spectator/TestDefaultTagFinder.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.servicecomb.foundation.metrics.publish.spectator;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import com.netflix.spectator.api.BasicTag;
+import com.netflix.spectator.api.Tag;
+
+public class TestDefaultTagFinder {
+  TagFinder finder = new DefaultTagFinder("key");
+
+  @Test
+  public void getTagKey() {
+    Assert.assertEquals("key", finder.getTagKey());
+  }
+
+  @Test
+  public void readSucc() {
+    Tag tag = new BasicTag("key", "value");
+    List<Tag> tags = Arrays.asList(new BasicTag("t1", "t1v"),
+        tag);
+
+    Assert.assertSame(tag, finder.find(tags));
+  }
+
+  @Test
+  public void readFail() {
+    List<Tag> tags = Arrays.asList(new BasicTag("t1", "t1v"));
+
+    Assert.assertNull(finder.find(tags));
+  }
+}
diff --git a/foundations/foundation-metrics/src/test/java/org/apache/servicecomb/foundation/metrics/publish/spectator/TestMeasurementGroupConfig.java b/foundations/foundation-metrics/src/test/java/org/apache/servicecomb/foundation/metrics/publish/spectator/TestMeasurementGroupConfig.java
new file mode 100644
index 000000000..51b6a56ab
--- /dev/null
+++ b/foundations/foundation-metrics/src/test/java/org/apache/servicecomb/foundation/metrics/publish/spectator/TestMeasurementGroupConfig.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.servicecomb.foundation.metrics.publish.spectator;
+
+import java.util.List;
+import java.util.Map;
+
+import org.hamcrest.Matchers;
+import org.junit.Assert;
+import org.junit.Test;
+
+import mockit.Deencapsulation;
+
+public class TestMeasurementGroupConfig {
+  MeasurementGroupConfig config = new MeasurementGroupConfig();
+
+  Map<String, List<TagFinder>> groups = Deencapsulation.getField(config, "groups");
+
+  @Test
+  public void defaultConstruct() {
+    Assert.assertTrue(groups.isEmpty());
+  }
+
+  @Test
+  public void constructAddGroup() {
+    config = new MeasurementGroupConfig("id", "tag1");
+    groups = Deencapsulation.getField(config, "groups");
+
+    Assert.assertThat(groups.keySet(), Matchers.contains("id"));
+    Assert.assertThat(groups.get("id").stream().map(e -> {
+      return e.getTagKey();
+    }).toArray(), Matchers.arrayContaining("tag1"));
+  }
+
+  @Test
+  public void addGroup() {
+    config.addGroup("id1", "tag1.1", "tag1.2");
+    config.addGroup("id2", "tag2.1", "tag2.2");
+
+    Assert.assertThat(groups.keySet(), Matchers.contains("id2", "id1"));
+    Assert.assertThat(groups.get("id1").stream().map(e -> {
+      return e.getTagKey();
+    }).toArray(), Matchers.arrayContaining("tag1.1", "tag1.2"));
+    Assert.assertThat(groups.get("id2").stream().map(e -> {
+      return e.getTagKey();
+    }).toArray(), Matchers.arrayContaining("tag2.1", "tag2.2"));
+  }
+
+  @Test
+  public void findTagReaders() {
+    config.addGroup("id1", "tag1.1", "tag1.2");
+    config.addGroup("id2", "tag2.1", "tag2.2");
+
+    Assert.assertThat(config.findTagFinders("id2").stream().map(e -> {
+      return e.getTagKey();
+    }).toArray(), Matchers.arrayContaining("tag2.1", "tag2.2"));
+  }
+}
diff --git a/foundations/foundation-metrics/src/test/java/org/apache/servicecomb/foundation/metrics/publish/spectator/TestMeasurementNode.java b/foundations/foundation-metrics/src/test/java/org/apache/servicecomb/foundation/metrics/publish/spectator/TestMeasurementNode.java
new file mode 100644
index 000000000..cf55e5684
--- /dev/null
+++ b/foundations/foundation-metrics/src/test/java/org/apache/servicecomb/foundation/metrics/publish/spectator/TestMeasurementNode.java
@@ -0,0 +1,82 @@
+/*
+ * 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.servicecomb.foundation.metrics.publish.spectator;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.hamcrest.Matchers;
+import org.junit.Assert;
+import org.junit.Test;
+
+import com.netflix.spectator.api.Measurement;
+
+import mockit.Mocked;
+
+public class TestMeasurementNode {
+  MeasurementNode node = new MeasurementNode("name", null);
+
+  @Test
+  public void getName() {
+    Assert.assertEquals("name", node.getName());
+  }
+
+  @Test
+  public void getChildren() {
+    Map<String, MeasurementNode> children = new HashMap<>();
+    node = new MeasurementNode("name", children);
+
+    Assert.assertSame(children, node.getChildren());
+  }
+
+  @Test
+  public void findChild_noChildren() {
+    Assert.assertNull(node.findChild("child"));
+  }
+
+  @Test
+  public void findChild_multiLevel_noMiddleChildren(@Mocked Measurement measurement) {
+    MeasurementNode c1 = node.addChild("c1", measurement);
+    c1.addChild("c2", measurement);
+
+    Assert.assertNull(node.findChild("c1_notExist", "c2"));
+  }
+
+  @Test
+  public void findChild_multiLevel_ok(@Mocked Measurement measurement) {
+    MeasurementNode c1 = node.addChild("c1", measurement);
+    MeasurementNode c2 = c1.addChild("c2", measurement);
+
+    Assert.assertSame(c2, node.findChild("c1", "c2"));
+  }
+
+  @Test
+  public void addChild(@Mocked Measurement measurement) {
+    MeasurementNode c1 = node.addChild("c1", measurement);
+    MeasurementNode c2 = node.addChild("c2", measurement);
+
+    Assert.assertSame(c1, node.findChild("c1"));
+    Assert.assertSame(c2, node.findChild("c2"));
+  }
+
+  @Test
+  public void getMeasurements(@Mocked Measurement measurement) {
+    node.addMeasurement(measurement);
+
+    Assert.assertThat(node.getMeasurements(), Matchers.contains(measurement));
+  }
+}
diff --git a/foundations/foundation-metrics/src/test/java/org/apache/servicecomb/foundation/metrics/publish/spectator/TestMeasurementTree.java b/foundations/foundation-metrics/src/test/java/org/apache/servicecomb/foundation/metrics/publish/spectator/TestMeasurementTree.java
new file mode 100644
index 000000000..a3eb50d59
--- /dev/null
+++ b/foundations/foundation-metrics/src/test/java/org/apache/servicecomb/foundation/metrics/publish/spectator/TestMeasurementTree.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.servicecomb.foundation.metrics.publish.spectator;
+
+import java.util.concurrent.TimeUnit;
+
+import org.hamcrest.Matchers;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import com.netflix.spectator.api.DefaultRegistry;
+import com.netflix.spectator.api.ManualClock;
+import com.netflix.spectator.api.Registry;
+import com.netflix.spectator.api.Statistic;
+import com.netflix.spectator.api.Timer;
+
+public class TestMeasurementTree {
+  MeasurementTree tree = new MeasurementTree();
+
+  ManualClock clock = new ManualClock();
+
+  Registry registry = new DefaultRegistry(clock);
+
+  Timer timer;
+
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  @Before
+  public void setup() {
+    timer = registry.timer("id",
+        "g1",
+        "g1v",
+        "g2",
+        "g2v",
+        "t3",
+        "t3v",
+        "t4",
+        "t4v");
+    registry.counter("id_notCare");
+  }
+
+  @Test
+  public void from() {
+    timer.record(10, TimeUnit.NANOSECONDS);
+    timer.record(2, TimeUnit.NANOSECONDS);
+
+    MeasurementGroupConfig config = new MeasurementGroupConfig("id", "g1", "g2", Statistic.count.key());
+    tree.from(registry.iterator(), config);
+
+    Assert.assertEquals(1, tree.getChildren().size());
+
+    MeasurementNode node = tree.findChild("id", "g1v", "g2v");
+    Assert.assertEquals(2d, node.findChild(Statistic.count.value()).getMeasurements().get(0).value(), 0);
+    Assert.assertEquals(12d, node.findChild(Statistic.totalTime.value()).getMeasurements().get(0).value(), 0);
+  }
+
+  @Test
+  public void from_failed() {
+    expectedException.expect(IllegalStateException.class);
+    expectedException.expectMessage(Matchers
+        .is("tag key \"notExist\" not exist in Measurement(id:g1=g1v:g2=g2v:statistic=count:t3=t3v:t4=t4v,0,0.0)"));
+
+    MeasurementGroupConfig config = new MeasurementGroupConfig("id", "notExist");
+    tree.from(registry.iterator(), config);
+  }
+}
diff --git a/foundations/foundation-metrics/src/test/java/org/apache/servicecomb/foundation/metrics/publish/spectator/TestTagFinder.java b/foundations/foundation-metrics/src/test/java/org/apache/servicecomb/foundation/metrics/publish/spectator/TestTagFinder.java
new file mode 100644
index 000000000..1f74e1944
--- /dev/null
+++ b/foundations/foundation-metrics/src/test/java/org/apache/servicecomb/foundation/metrics/publish/spectator/TestTagFinder.java
@@ -0,0 +1,61 @@
+/*
+ * 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.servicecomb.foundation.metrics.publish.spectator;
+
+import org.hamcrest.Matchers;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+public class TestTagFinder {
+  @Rule
+  public ExpectedException expectedException = ExpectedException.none();
+
+  @Test
+  public void buildFromString() {
+    String name = "key";
+    TagFinder finder = TagFinder.build(name);
+
+    Assert.assertEquals(name, finder.getTagKey());
+    Assert.assertEquals(DefaultTagFinder.class, finder.getClass());
+  }
+
+  @Test
+  public void buildFromTagFinder() {
+    TagFinder finder = new DefaultTagFinder("key");
+    Assert.assertSame(finder, TagFinder.build(finder));
+  }
+
+  @Test
+  public void buildFromInvalidType() {
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException
+        .expectMessage(Matchers.is("only support String or TagFinder, but got " + Integer.class.getName()));
+
+    TagFinder.build(1);
+  }
+
+  @Test
+  public void buildFromNull() {
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException
+        .expectMessage(Matchers.is("only support String or TagFinder, but got null"));
+
+    TagFinder.build(null);
+  }
+}
diff --git a/java-chassis-dependencies/pom.xml b/java-chassis-dependencies/pom.xml
index 3846dd716..8739033b9 100644
--- a/java-chassis-dependencies/pom.xml
+++ b/java-chassis-dependencies/pom.xml
@@ -357,6 +357,11 @@
           </exclusion>
         </exclusions>
       </dependency>
+      <dependency>
+        <groupId>com.netflix.spectator</groupId>
+        <artifactId>spectator-reg-servo</artifactId>
+        <version>0.62.0</version>
+      </dependency>
       <dependency>
         <groupId>com.google.inject</groupId>
         <artifactId>guice</artifactId>
diff --git a/java-chassis-distribution/src/release/LICENSE b/java-chassis-distribution/src/release/LICENSE
index b49905d9c..8fb6c3663 100644
--- a/java-chassis-distribution/src/release/LICENSE
+++ b/java-chassis-distribution/src/release/LICENSE
@@ -432,6 +432,8 @@ rxnetty-contexts (https://github.com/ReactiveX/RxNetty) io.reactivex:rxnetty-con
 rxnetty-servo (https://github.com/ReactiveX/RxNetty) io.reactivex:rxnetty-servo:jar:0.4.9
 servo-core (https://github.com/Netflix/servo) com.netflix.servo:servo-core:jar:0.10.1
 servo-internal (https://github.com/Netflix/servo) com.netflix.servo:servo-internal:jar:0.10.1
+spectator-reg-servo (https://github.com/Netflix/spectator) com.netflix.spectator:spectator-reg-servo:0.62.0
+spectator-api (https://github.com/Netflix/spectator) com.netflix.spectator:spectator-api:0.62.0
 spring-cloud-starter (https://projects.spring.io/spring-cloud) org.springframework.cloud:spring-cloud-starter:jar:1.1.8.RELEASE
 spring-cloud-starter-archaius (https://projects.spring.io/spring-cloud) org.springframework.cloud:spring-cloud-starter-archaius:jar:1.2.6.RELEASE
 spring-cloud-starter-hystrix (https://projects.spring.io/spring-cloud) org.springframework.cloud:spring-cloud-starter-hystrix:jar:1.2.6.RELEASE


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services