You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by ab...@apache.org on 2016/12/20 09:12:37 UTC

[1/3] lucene-solr:master: Squashed commit of branch 'feature/metrics', containing: SOLR-4735: Improve Solr metrics reporting SOLR-9812: Implement /admin/metrics API SOLR-9805: Use metrics-jvm library to instrument jvm internals SOLR-9788:

Repository: lucene-solr
Updated Branches:
  refs/heads/master 84bbb8f79 -> 8bbdb6248


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8bbdb624/solr/core/src/test/org/apache/solr/util/stats/MetricUtilsTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/util/stats/MetricUtilsTest.java b/solr/core/src/test/org/apache/solr/util/stats/MetricUtilsTest.java
new file mode 100644
index 0000000..31e8154
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/util/stats/MetricUtilsTest.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.solr.util.stats;
+
+import java.util.concurrent.TimeUnit;
+
+import com.codahale.metrics.Snapshot;
+import com.codahale.metrics.Timer;
+import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.common.util.NamedList;
+import org.apache.solr.common.util.SimpleOrderedMap;
+import org.junit.Test;
+
+public class MetricUtilsTest extends SolrTestCaseJ4 {
+
+  @Test
+  public void testSolrTimerGetSnapshot() {
+    // create a timer with up to 100 data points
+    final Timer timer = new Timer();
+    final int iterations = random().nextInt(100);
+    for (int i = 0; i < iterations; ++i) {
+      timer.update(random().nextInt(), TimeUnit.NANOSECONDS);
+    }
+    // obtain timer metrics
+    final NamedList<Object> lst = new SimpleOrderedMap<>();
+    MetricUtils.addMetrics(lst, timer);
+    // check that expected metrics were obtained
+    assertEquals(lst.size(), 9);
+    final Snapshot snapshot = timer.getSnapshot();
+    // cannot test avgRequestsPerMinute directly because mean rate changes as time increases!
+    // assertEquals(lst.get("avgRequestsPerSecond"), timer.getMeanRate());
+    assertEquals(lst.get("5minRateRequestsPerSecond"), timer.getFiveMinuteRate());
+    assertEquals(lst.get("15minRateRequestsPerSecond"), timer.getFifteenMinuteRate());
+    assertEquals(lst.get("avgTimePerRequest"), MetricUtils.nsToMs(snapshot.getMean()));
+    assertEquals(lst.get("medianRequestTime"), MetricUtils.nsToMs(snapshot.getMedian()));
+    assertEquals(lst.get("75thPcRequestTime"), MetricUtils.nsToMs(snapshot.get75thPercentile()));
+    assertEquals(lst.get("95thPcRequestTime"), MetricUtils.nsToMs(snapshot.get95thPercentile()));
+    assertEquals(lst.get("99thPcRequestTime"), MetricUtils.nsToMs(snapshot.get99thPercentile()));
+    assertEquals(lst.get("999thPcRequestTime"), MetricUtils.nsToMs(snapshot.get999thPercentile()));
+  }
+
+}
+

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8bbdb624/solr/core/src/test/org/apache/solr/util/stats/TimerUtilsTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/util/stats/TimerUtilsTest.java b/solr/core/src/test/org/apache/solr/util/stats/TimerUtilsTest.java
deleted file mode 100644
index 851f768..0000000
--- a/solr/core/src/test/org/apache/solr/util/stats/TimerUtilsTest.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * 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.solr.util.stats;
-
-import java.util.concurrent.TimeUnit;
-
-import com.codahale.metrics.Snapshot;
-import com.codahale.metrics.Timer;
-import org.apache.solr.SolrTestCaseJ4;
-import org.apache.solr.common.util.NamedList;
-import org.apache.solr.common.util.SimpleOrderedMap;
-import org.junit.Test;
-
-public class TimerUtilsTest extends SolrTestCaseJ4 {
-
-  @Test
-  public void testSolrTimerGetSnapshot() {
-    // create a timer with up to 100 data points
-    final Timer timer = new Timer();
-    final int iterations = random().nextInt(100);
-    for (int i = 0; i < iterations; ++i) {
-      timer.update(random().nextInt(), TimeUnit.NANOSECONDS);
-    }
-    // obtain timer metrics
-    final NamedList<Object> lst = new SimpleOrderedMap<>();
-    TimerUtils.addMetrics(lst, timer);
-    // check that expected metrics were obtained
-    assertEquals(lst.size(), 9);
-    final Snapshot snapshot = timer.getSnapshot();
-    // cannot test avgRequestsPerMinute directly because mean rate changes as time increases!
-    // assertEquals(lst.get("avgRequestsPerSecond"), timer.getMeanRate());
-    assertEquals(lst.get("5minRateRequestsPerSecond"), timer.getFiveMinuteRate());
-    assertEquals(lst.get("15minRateRequestsPerSecond"), timer.getFifteenMinuteRate());
-    assertEquals(lst.get("avgTimePerRequest"), TimerUtils.nsToMs(snapshot.getMean()));
-    assertEquals(lst.get("medianRequestTime"), TimerUtils.nsToMs(snapshot.getMedian()));
-    assertEquals(lst.get("75thPcRequestTime"), TimerUtils.nsToMs(snapshot.get75thPercentile()));
-    assertEquals(lst.get("95thPcRequestTime"), TimerUtils.nsToMs(snapshot.get95thPercentile()));
-    assertEquals(lst.get("99thPcRequestTime"), TimerUtils.nsToMs(snapshot.get99thPercentile()));
-    assertEquals(lst.get("999thPcRequestTime"), TimerUtils.nsToMs(snapshot.get999thPercentile()));
-  }
-
-}
-

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8bbdb624/solr/licenses/metrics-jetty-LICENSE-ASL.txt
----------------------------------------------------------------------
diff --git a/solr/licenses/metrics-jetty-LICENSE-ASL.txt b/solr/licenses/metrics-jetty-LICENSE-ASL.txt
new file mode 100644
index 0000000..ccb320c
--- /dev/null
+++ b/solr/licenses/metrics-jetty-LICENSE-ASL.txt
@@ -0,0 +1,203 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright 2010-2012 Coda Hale and Yammer, Inc.
+
+   Licensed 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.
+

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8bbdb624/solr/licenses/metrics-jetty-NOTICE.txt
----------------------------------------------------------------------
diff --git a/solr/licenses/metrics-jetty-NOTICE.txt b/solr/licenses/metrics-jetty-NOTICE.txt
new file mode 100644
index 0000000..b4c6298
--- /dev/null
+++ b/solr/licenses/metrics-jetty-NOTICE.txt
@@ -0,0 +1,12 @@
+Metrics
+Copyright 2010-2013 Coda Hale and Yammer, Inc.
+
+This product includes software developed by Coda Hale and Yammer, Inc.
+
+This product includes code derived from the JSR-166 project (ThreadLocalRandom, Striped64,
+LongAdder), which was released with the following comments:
+
+    Written by Doug Lea with assistance from members of JCP JSR-166
+    Expert Group and released to the public domain, as explained at
+    http://creativecommons.org/publicdomain/zero/1.0/
+

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8bbdb624/solr/licenses/metrics-jetty9-3.1.2.jar.sha1
----------------------------------------------------------------------
diff --git a/solr/licenses/metrics-jetty9-3.1.2.jar.sha1 b/solr/licenses/metrics-jetty9-3.1.2.jar.sha1
new file mode 100644
index 0000000..0722b0b
--- /dev/null
+++ b/solr/licenses/metrics-jetty9-3.1.2.jar.sha1
@@ -0,0 +1 @@
+7f2fe1039424ca687bea5d09ec0bfa372bf7d062

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8bbdb624/solr/licenses/metrics-json-LICENSE-ASL.txt
----------------------------------------------------------------------
diff --git a/solr/licenses/metrics-json-LICENSE-ASL.txt b/solr/licenses/metrics-json-LICENSE-ASL.txt
new file mode 100644
index 0000000..ccb320c
--- /dev/null
+++ b/solr/licenses/metrics-json-LICENSE-ASL.txt
@@ -0,0 +1,203 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright 2010-2012 Coda Hale and Yammer, Inc.
+
+   Licensed 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.
+

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8bbdb624/solr/licenses/metrics-json-NOTICE.txt
----------------------------------------------------------------------
diff --git a/solr/licenses/metrics-json-NOTICE.txt b/solr/licenses/metrics-json-NOTICE.txt
new file mode 100644
index 0000000..b4c6298
--- /dev/null
+++ b/solr/licenses/metrics-json-NOTICE.txt
@@ -0,0 +1,12 @@
+Metrics
+Copyright 2010-2013 Coda Hale and Yammer, Inc.
+
+This product includes software developed by Coda Hale and Yammer, Inc.
+
+This product includes code derived from the JSR-166 project (ThreadLocalRandom, Striped64,
+LongAdder), which was released with the following comments:
+
+    Written by Doug Lea with assistance from members of JCP JSR-166
+    Expert Group and released to the public domain, as explained at
+    http://creativecommons.org/publicdomain/zero/1.0/
+

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8bbdb624/solr/licenses/metrics-jvm-3.1.2.jar.sha1
----------------------------------------------------------------------
diff --git a/solr/licenses/metrics-jvm-3.1.2.jar.sha1 b/solr/licenses/metrics-jvm-3.1.2.jar.sha1
new file mode 100644
index 0000000..519fcdd
--- /dev/null
+++ b/solr/licenses/metrics-jvm-3.1.2.jar.sha1
@@ -0,0 +1 @@
+ed364e77218e50fdcdebce4d982cb4d1f4a8c187

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8bbdb624/solr/licenses/metrics-jvm-LICENSE-ASL.txt
----------------------------------------------------------------------
diff --git a/solr/licenses/metrics-jvm-LICENSE-ASL.txt b/solr/licenses/metrics-jvm-LICENSE-ASL.txt
new file mode 100644
index 0000000..ccb320c
--- /dev/null
+++ b/solr/licenses/metrics-jvm-LICENSE-ASL.txt
@@ -0,0 +1,203 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright 2010-2012 Coda Hale and Yammer, Inc.
+
+   Licensed 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.
+

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8bbdb624/solr/licenses/metrics-jvm-NOTICE.txt
----------------------------------------------------------------------
diff --git a/solr/licenses/metrics-jvm-NOTICE.txt b/solr/licenses/metrics-jvm-NOTICE.txt
new file mode 100644
index 0000000..b4c6298
--- /dev/null
+++ b/solr/licenses/metrics-jvm-NOTICE.txt
@@ -0,0 +1,12 @@
+Metrics
+Copyright 2010-2013 Coda Hale and Yammer, Inc.
+
+This product includes software developed by Coda Hale and Yammer, Inc.
+
+This product includes code derived from the JSR-166 project (ThreadLocalRandom, Striped64,
+LongAdder), which was released with the following comments:
+
+    Written by Doug Lea with assistance from members of JCP JSR-166
+    Expert Group and released to the public domain, as explained at
+    http://creativecommons.org/publicdomain/zero/1.0/
+

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8bbdb624/solr/licenses/metrics-servlets-LICENSE-ASL.txt
----------------------------------------------------------------------
diff --git a/solr/licenses/metrics-servlets-LICENSE-ASL.txt b/solr/licenses/metrics-servlets-LICENSE-ASL.txt
new file mode 100644
index 0000000..ccb320c
--- /dev/null
+++ b/solr/licenses/metrics-servlets-LICENSE-ASL.txt
@@ -0,0 +1,203 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright 2010-2012 Coda Hale and Yammer, Inc.
+
+   Licensed 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.
+

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8bbdb624/solr/licenses/metrics-servlets-NOTICE.txt
----------------------------------------------------------------------
diff --git a/solr/licenses/metrics-servlets-NOTICE.txt b/solr/licenses/metrics-servlets-NOTICE.txt
new file mode 100644
index 0000000..b4c6298
--- /dev/null
+++ b/solr/licenses/metrics-servlets-NOTICE.txt
@@ -0,0 +1,12 @@
+Metrics
+Copyright 2010-2013 Coda Hale and Yammer, Inc.
+
+This product includes software developed by Coda Hale and Yammer, Inc.
+
+This product includes code derived from the JSR-166 project (ThreadLocalRandom, Striped64,
+LongAdder), which was released with the following comments:
+
+    Written by Doug Lea with assistance from members of JCP JSR-166
+    Expert Group and released to the public domain, as explained at
+    http://creativecommons.org/publicdomain/zero/1.0/
+

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8bbdb624/solr/server/build.xml
----------------------------------------------------------------------
diff --git a/solr/server/build.xml b/solr/server/build.xml
index 8600e17..ae4e650 100644
--- a/solr/server/build.xml
+++ b/solr/server/build.xml
@@ -39,8 +39,8 @@
   <target name="resolve" depends="ivy-availability-check,ivy-fail,ivy-configure">
     <sequential>
     <!-- jetty libs in lib/ -->
-    <ivy:retrieve conf="jetty,servlet" type="jar" log="download-only" symlink="${ivy.symlink}"
-                pattern="lib/[artifact]-[revision].[ext]" sync="${ivy.sync}"/>
+    <ivy:retrieve conf="jetty,servlet,metrics" type="jar,bundle" log="download-only" symlink="${ivy.symlink}"
+                  pattern="lib/[artifact]-[revision].[ext]" sync="${ivy.sync}"/>
     <ivy:retrieve conf="logging" type="jar,bundle" log="download-only" symlink="${ivy.symlink}"
                   pattern="lib/ext/[artifact]-[revision].[ext]" sync="${ivy.sync}"/>
     <!-- start.jar - we don't use sync=true here, we don't own the dir, but

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8bbdb624/solr/server/etc/jetty.xml
----------------------------------------------------------------------
diff --git a/solr/server/etc/jetty.xml b/solr/server/etc/jetty.xml
index f1b94c8..8cb8223 100644
--- a/solr/server/etc/jetty.xml
+++ b/solr/server/etc/jetty.xml
@@ -29,9 +29,16 @@
   <!-- Consult the javadoc of o.e.j.util.thread.QueuedThreadPool   -->
   <!-- for all configuration that may be set here.                 -->
   <!-- =========================================================== -->
-  <!-- uncomment to change type of threadpool
-  <Arg name="threadpool"><New id="threadpool" class="org.eclipse.jetty.util.thread.QueuedThreadPool"/></Arg>
-  -->
+  <Arg name="threadpool">
+    <New id="threadpool" class="com.codahale.metrics.jetty9.InstrumentedQueuedThreadPool">
+      <Arg name="registry">
+        <Call id="solrJettyMetricRegistry" name="getOrCreate" class="com.codahale.metrics.SharedMetricRegistries">
+          <Arg>solr.jetty</Arg>
+        </Call>
+      </Arg>
+    </New>
+  </Arg>
+
   <Get name="ThreadPool">
     <Set name="minThreads" type="int"><Property name="solr.jetty.threads.min" default="10"/></Set>
     <Set name="maxThreads" type="int"><Property name="solr.jetty.threads.max" default="10000"/></Set>
@@ -106,7 +113,12 @@
              <New id="Contexts" class="org.eclipse.jetty.server.handler.ContextHandlerCollection"/>
            </Item>
            <Item>
-             <New id="DefaultHandler" class="org.eclipse.jetty.server.handler.DefaultHandler"/>
+             <New id="InstrumentedHandler" class="com.codahale.metrics.jetty9.InstrumentedHandler">
+               <Arg><Ref refid="solrJettyMetricRegistry"/></Arg>
+               <Set name="handler">
+                 <New id="DefaultHandler" class="org.eclipse.jetty.server.handler.DefaultHandler"/>
+               </Set>
+             </New>
            </Item>
            <Item>
              <New id="RequestLog" class="org.eclipse.jetty.server.handler.RequestLogHandler"/>

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8bbdb624/solr/server/ivy.xml
----------------------------------------------------------------------
diff --git a/solr/server/ivy.xml b/solr/server/ivy.xml
index 3a48224..8dc645a 100644
--- a/solr/server/ivy.xml
+++ b/solr/server/ivy.xml
@@ -18,7 +18,8 @@
 -->
 <ivy-module version="2.0">
   <info organisation="org.apache.solr" module="server"/>
-  <configurations defaultconfmapping="jetty->master;start->master;servlet->master;logging->master">
+  <configurations defaultconfmapping="metrics->master;jetty->master;start->master;servlet->master;logging->master">
+    <conf name="metrics" description="metrics jars" transitive="true"/>
     <conf name="jetty" description="jetty jars" transitive="false"/>
     <conf name="start" description="jetty start jar" transitive="false"/>
     <conf name="servlet" description="servlet-api jar" transitive="false"/>
@@ -30,8 +31,12 @@
     <dependency org="org.slf4j" name="slf4j-api" rev="${/org.slf4j/slf4j-api}" conf="logging"/>
     <dependency org="org.slf4j" name="jcl-over-slf4j" rev="${/org.slf4j/jcl-over-slf4j}" conf="logging"/>
     <dependency org="org.slf4j" name="jul-to-slf4j" rev="${/org.slf4j/jul-to-slf4j}" conf="logging"/> 
-    <dependency org="org.slf4j" name="slf4j-log4j12" rev="${/org.slf4j/slf4j-log4j12}" conf="logging"/> 
-      
+    <dependency org="org.slf4j" name="slf4j-log4j12" rev="${/org.slf4j/slf4j-log4j12}" conf="logging"/>
+
+    <dependency org="io.dropwizard.metrics" name="metrics-core" rev="${/io.dropwizard.metrics/metrics-core}" conf="metrics" />
+    <dependency org="io.dropwizard.metrics" name="metrics-jetty9" rev="${/io.dropwizard.metrics/metrics-jetty9}" conf="metrics" />
+    <dependency org="io.dropwizard.metrics" name="metrics-jvm" rev="${/io.dropwizard.metrics/metrics-jvm}" conf="metrics" />
+
     <dependency org="org.eclipse.jetty" name="jetty-continuation" rev="${/org.eclipse.jetty/jetty-continuation}" conf="jetty"/>
     <dependency org="org.eclipse.jetty" name="jetty-deploy" rev="${/org.eclipse.jetty/jetty-deploy}" conf="jetty"/>
     <dependency org="org.eclipse.jetty" name="jetty-http" rev="${/org.eclipse.jetty/jetty-http}" conf="jetty"/>

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8bbdb624/solr/solrj/src/java/org/apache/solr/common/params/CommonParams.java
----------------------------------------------------------------------
diff --git a/solr/solrj/src/java/org/apache/solr/common/params/CommonParams.java b/solr/solrj/src/java/org/apache/solr/common/params/CommonParams.java
index 411d40d..7cf27d2 100644
--- a/solr/solrj/src/java/org/apache/solr/common/params/CommonParams.java
+++ b/solr/solrj/src/java/org/apache/solr/common/params/CommonParams.java
@@ -180,13 +180,15 @@ public interface CommonParams {
   String AUTHZ_PATH = "/admin/authorization";
   String AUTHC_PATH = "/admin/authentication";
   String ZK_PATH = "/admin/zookeeper";
+  String METRICS_PATH = "/admin/metrics";
 
   Set<String> ADMIN_PATHS = new HashSet<>(Arrays.asList(
       CORES_HANDLER_PATH,
       COLLECTIONS_HANDLER_PATH,
       CONFIGSETS_HANDLER_PATH,
       AUTHC_PATH,
-      AUTHZ_PATH));
+      AUTHZ_PATH,
+      METRICS_PATH));
 
   /** valid values for: <code>echoParams</code> */
   enum EchoParamStyle {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8bbdb624/solr/test-framework/src/java/org/apache/solr/util/TestHarness.java
----------------------------------------------------------------------
diff --git a/solr/test-framework/src/java/org/apache/solr/util/TestHarness.java b/solr/test-framework/src/java/org/apache/solr/util/TestHarness.java
index 261d2ec..be8a24c 100644
--- a/solr/test-framework/src/java/org/apache/solr/util/TestHarness.java
+++ b/solr/test-framework/src/java/org/apache/solr/util/TestHarness.java
@@ -37,11 +37,13 @@ import org.apache.solr.core.CoreDescriptor;
 import org.apache.solr.core.CorePropertiesLocator;
 import org.apache.solr.core.CoresLocator;
 import org.apache.solr.core.NodeConfig;
+import org.apache.solr.core.PluginInfo;
 import org.apache.solr.core.SolrConfig;
 import org.apache.solr.core.SolrCore;
 import org.apache.solr.core.SolrResourceLoader;
 import org.apache.solr.core.SolrXmlConfig;
 import org.apache.solr.handler.UpdateRequestHandler;
+import org.apache.solr.metrics.reporters.SolrJmxReporter;
 import org.apache.solr.request.LocalSolrQueryRequest;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.request.SolrRequestHandler;
@@ -189,10 +191,17 @@ public class TestHarness extends BaseTestHarness {
         = new UpdateShardHandlerConfig(UpdateShardHandlerConfig.DEFAULT_MAXUPDATECONNECTIONS,
                                        UpdateShardHandlerConfig.DEFAULT_MAXUPDATECONNECTIONSPERHOST,
                                        30000, 30000);
+    // universal default metric reporter
+    Map<String,String> attributes = new HashMap<>();
+    attributes.put("name", "default");
+    attributes.put("class", SolrJmxReporter.class.getName());
+    PluginInfo defaultPlugin = new PluginInfo("reporter", attributes, null, null);
+
     return new NodeConfig.NodeConfigBuilder("testNode", loader)
         .setUseSchemaCache(Boolean.getBoolean("shareSchema"))
         .setCloudConfig(cloudConfig)
         .setUpdateShardHandlerConfig(updateShardHandlerConfig)
+        .setMetricReporterPlugins(new PluginInfo[] {defaultPlugin})
         .build();
   }
 


[2/3] lucene-solr:master: Squashed commit of branch 'feature/metrics', containing: SOLR-4735: Improve Solr metrics reporting SOLR-9812: Implement /admin/metrics API SOLR-9805: Use metrics-jvm library to instrument jvm internals SOLR-9788:

Posted by ab...@apache.org.
http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8bbdb624/solr/core/src/java/org/apache/solr/metrics/SolrMetricReporter.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/metrics/SolrMetricReporter.java b/solr/core/src/java/org/apache/solr/metrics/SolrMetricReporter.java
new file mode 100644
index 0000000..ff2d3fc
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/metrics/SolrMetricReporter.java
@@ -0,0 +1,83 @@
+/*
+ * 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.solr.metrics;
+
+import java.io.Closeable;
+
+import org.apache.solr.core.PluginInfo;
+import org.apache.solr.util.SolrPluginUtils;
+import org.apache.solr.util.plugin.PluginInfoInitialized;
+
+/**
+ * Interface for 'pluggable' metric reporters.
+ */
+public abstract class SolrMetricReporter implements Closeable, PluginInfoInitialized {
+
+  protected final String registryName;
+  protected final SolrMetricManager metricManager;
+  protected PluginInfo pluginInfo;
+
+  /**
+   * Create a reporter for metrics managed in a named registry.
+   * @param registryName registry to use, one of registries managed by
+   *                     {@link SolrMetricManager}
+   */
+  protected SolrMetricReporter(SolrMetricManager metricManager, String registryName) {
+    this.registryName = registryName;
+    this.metricManager = metricManager;
+  }
+
+  /**
+   * Initializes a {@link SolrMetricReporter} with the plugin's configuration.
+   *
+   * @param pluginInfo the plugin's configuration
+   */
+  @SuppressWarnings("unchecked")
+  public void init(PluginInfo pluginInfo) {
+    if (pluginInfo != null) {
+      this.pluginInfo = pluginInfo.copy();
+      if (this.pluginInfo.initArgs != null) {
+        SolrPluginUtils.invokeSetters(this, this.pluginInfo.initArgs);
+      }
+    }
+    validate();
+  }
+
+  /**
+   * Get the effective {@link PluginInfo} instance that was used for
+   * initialization of this plugin.
+   * @return plugin info, or null if not yet initialized.
+   */
+  public PluginInfo getPluginInfo() {
+    return pluginInfo;
+  }
+
+  /**
+   * Validates that the reporter has been correctly configured.
+   *
+   * @throws IllegalStateException if the reporter is not properly configured
+   */
+  protected abstract void validate() throws IllegalStateException;
+
+  @Override
+  public String toString() {
+    return getClass().getName() + "{" +
+        "registryName='" + registryName + '\'' +
+        ", pluginInfo=" + pluginInfo +
+        '}';
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8bbdb624/solr/core/src/java/org/apache/solr/metrics/package-info.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/metrics/package-info.java b/solr/core/src/java/org/apache/solr/metrics/package-info.java
new file mode 100644
index 0000000..a007b8b
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/metrics/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+
+/**
+ * The {@link org.apache.solr.metrics.SolrCoreMetricManager} is responsible for
+ * collecting metrics from {@link org.apache.solr.metrics.SolrMetricProducer}'s
+ * and exposing metrics to {@link org.apache.solr.metrics.SolrMetricReporter}'s.
+ */
+package org.apache.solr.metrics;

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8bbdb624/solr/core/src/java/org/apache/solr/metrics/reporters/SolrJmxReporter.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/metrics/reporters/SolrJmxReporter.java b/solr/core/src/java/org/apache/solr/metrics/reporters/SolrJmxReporter.java
new file mode 100644
index 0000000..47fbf11
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/metrics/reporters/SolrJmxReporter.java
@@ -0,0 +1,284 @@
+/*
+ * 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.solr.metrics.reporters;
+
+import javax.management.MBeanServer;
+import javax.management.MalformedObjectNameException;
+import javax.management.ObjectName;
+
+import java.io.IOException;
+import java.lang.invoke.MethodHandles;
+import java.lang.management.ManagementFactory;
+import java.util.Locale;
+
+import com.codahale.metrics.JmxReporter;
+import com.codahale.metrics.ObjectNameFactory;
+import org.apache.solr.core.PluginInfo;
+import org.apache.solr.metrics.SolrMetricInfo;
+import org.apache.solr.metrics.SolrMetricManager;
+import org.apache.solr.metrics.SolrMetricReporter;
+import org.apache.solr.util.JmxUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A {@link SolrMetricReporter} that finds (or creates) a MBeanServer from
+ * the given configuration and registers metrics to it with JMX.
+ */
+public class SolrJmxReporter extends SolrMetricReporter {
+
+  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+  private String domain;
+  private String agentId;
+  private String serviceUrl;
+
+  private JmxReporter reporter;
+  private MBeanServer mBeanServer;
+
+  /**
+   * Creates a new instance of {@link SolrJmxReporter}.
+   *
+   * @param registryName name of the registry to report
+   */
+  public SolrJmxReporter(SolrMetricManager metricManager, String registryName) {
+    super(metricManager, registryName);
+    setDomain(registryName);
+  }
+
+  /**
+   * Initializes the reporter by finding (or creating) a MBeanServer
+   * and registering the metricManager's metric registry.
+   *
+   * @param pluginInfo the configuration for the reporter
+   */
+  @Override
+  public synchronized void init(PluginInfo pluginInfo) {
+    super.init(pluginInfo);
+
+    if (serviceUrl != null && agentId != null) {
+      ManagementFactory.getPlatformMBeanServer(); // Ensure at least one MBeanServer is available.
+      mBeanServer = JmxUtil.findFirstMBeanServer();
+      log.warn("No more than one of serviceUrl(%s) and agentId(%s) should be configured, using first MBeanServer instead of configuration.",
+          serviceUrl, agentId, mBeanServer);
+    }
+    else if (serviceUrl != null) {
+      try {
+        mBeanServer = JmxUtil.findMBeanServerForServiceUrl(serviceUrl);
+      } catch (IOException e) {
+        log.warn("findMBeanServerForServiceUrl(%s) exception: %s", serviceUrl, e);
+        mBeanServer = null;
+      }
+    }
+    else if (agentId != null) {
+      mBeanServer = JmxUtil.findMBeanServerForAgentId(agentId);
+    } else {
+      ManagementFactory.getPlatformMBeanServer(); // Ensure at least one MBeanServer is available.
+      mBeanServer = JmxUtil.findFirstMBeanServer();
+      log.warn("No serviceUrl or agentId was configured, using first MBeanServer.", mBeanServer);
+    }
+
+    if (mBeanServer == null) {
+      log.warn("No JMX server found. Not exposing Solr metrics.");
+      return;
+    }
+
+    JmxObjectNameFactory jmxObjectNameFactory = new JmxObjectNameFactory(pluginInfo.name, domain);
+
+    reporter = JmxReporter.forRegistry(metricManager.registry(registryName))
+                          .registerWith(mBeanServer)
+                          .inDomain(domain)
+                          .createsObjectNamesWith(jmxObjectNameFactory)
+                          .build();
+    reporter.start();
+
+    log.info("JMX monitoring enabled at server: " + mBeanServer);
+  }
+
+  /**
+   * Stops the reporter from publishing metrics.
+   */
+  @Override
+  public synchronized void close() {
+    if (reporter != null) {
+      reporter.close();
+      reporter = null;
+    }
+  }
+
+  /**
+   * Validates that the reporter has been correctly configured.
+   * Note that all configurable arguments are currently optional.
+   *
+   * @throws IllegalStateException if the reporter is not properly configured
+   */
+  @Override
+  protected void validate() throws IllegalStateException {
+    // Nothing to validate
+  }
+
+  /**
+   * Sets the domain with which MBeans are published. If none is set,
+   * the domain defaults to the name of the core.
+   *
+   * @param domain the domain
+   */
+  public void setDomain(String domain) {
+    if (domain != null) {
+      this.domain = domain;
+    } else {
+      this.domain = registryName;
+    }
+  }
+
+  /**
+   * Sets the service url for a JMX server.
+   * Note that this configuration is optional.
+   *
+   * @param serviceUrl the service url
+   */
+  public void setServiceUrl(String serviceUrl) {
+    this.serviceUrl = serviceUrl;
+  }
+
+  /**
+   * Sets the agent id for a JMX server.
+   * Note that this configuration is optional.
+   *
+   * @param agentId the agent id
+   */
+  public void setAgentId(String agentId) {
+    this.agentId = agentId;
+  }
+
+  /**
+   * Retrieves the reporter's MBeanServer.
+   *
+   * @return the reporter's MBeanServer
+   */
+  public MBeanServer getMBeanServer() {
+    return mBeanServer;
+  }
+
+  @Override
+  public String toString() {
+    return String.format(Locale.ENGLISH, "[%s@%s: domain = %s, service url = %s, agent id = %s]",
+        getClass().getName(), Integer.toHexString(hashCode()), domain, serviceUrl, agentId);
+  }
+
+  /**
+   * Factory to create MBean names for a given metric.
+   */
+  private static class JmxObjectNameFactory implements ObjectNameFactory {
+
+    private final String domain;
+    private final String[] subdomains;
+    private final String reporterName;
+
+    JmxObjectNameFactory(String reporterName, String domain) {
+      this.reporterName = reporterName;
+      this.domain = domain;
+      this.subdomains = domain.split("\\.");
+    }
+
+    /**
+     * Create a hierarchical name of a metric.
+     *
+     * @param type    metric class, eg. "counters"
+     * @param currentDomain  JMX domain
+     * @param name    metric name
+     */
+    @Override
+    public ObjectName createName(String type, String currentDomain, String name) {
+      SolrMetricInfo metricInfo = SolrMetricInfo.of(name);
+
+      // It turns out that ObjectName(String) mostly preserves key ordering
+      // as specified in the constructor (except for the 'type' key that ends
+      // up at top level) - unlike ObjectName(String, Map) constructor
+      // that seems to have a mind of its own...
+      StringBuilder sb = new StringBuilder();
+      if (domain.equals(currentDomain)) {
+        if (subdomains != null && subdomains.length > 1) {
+          // use only first segment as domain
+          sb.append(subdomains[0]);
+          sb.append(':');
+          // use remaining segments as properties
+          for (int i = 1; i < subdomains.length; i++) {
+            if (i > 1) {
+              sb.append(',');
+            }
+            sb.append("dom");
+            sb.append(String.valueOf(i));
+            sb.append('=');
+            sb.append(subdomains[i]);
+          }
+          sb.append(','); // separate from other properties
+        } else {
+          sb.append(currentDomain);
+          sb.append(':');
+        }
+      } else {
+        sb.append(currentDomain);
+        sb.append(':');
+      }
+      sb.append("reporter=");
+      sb.append(reporterName);
+      sb.append(',');
+      if (metricInfo != null) {
+        sb.append("category=");
+        sb.append(metricInfo.category.toString());
+        sb.append(",scope=");
+        sb.append(metricInfo.scope);
+        // we could also split by type, but don't call it 'type' :)
+        // sb.append(",class=");
+        //sb.append(type);
+        sb.append(",name=");
+        sb.append(metricInfo.name);
+      } else {
+        // make dotted names into hierarchies
+        String[] path = name.split("\\.");
+        for (int i = 0; i < path.length - 1; i++) {
+          if (i > 0) {
+            sb.append(',');
+          }
+          sb.append("name"); sb.append(String.valueOf(i));
+          sb.append('=');
+          sb.append(path[i]);
+        }
+        if (path.length > 1) {
+          sb.append(',');
+        }
+        // split by type
+        // sb.append("class=");
+        // sb.append(type);
+        sb.append("name=");
+        sb.append(path[path.length - 1]);
+      }
+
+      ObjectName objectName;
+
+      try {
+        objectName = new ObjectName(sb.toString());
+      } catch (MalformedObjectNameException e) {
+        throw new RuntimeException(sb.toString(), e);
+      }
+
+      return objectName;
+    }
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8bbdb624/solr/core/src/java/org/apache/solr/metrics/reporters/package-info.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/metrics/reporters/package-info.java b/solr/core/src/java/org/apache/solr/metrics/reporters/package-info.java
new file mode 100644
index 0000000..5ad7a80
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/metrics/reporters/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * This package houses 'pluggable' metric reporters that
+ * inherit from the {@link org.apache.solr.metrics.SolrMetricReporter} class.
+ */
+package org.apache.solr.metrics.reporters;

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8bbdb624/solr/core/src/java/org/apache/solr/security/PermissionNameProvider.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/security/PermissionNameProvider.java b/solr/core/src/java/org/apache/solr/security/PermissionNameProvider.java
index d0e5c9a..425be38 100644
--- a/solr/core/src/java/org/apache/solr/security/PermissionNameProvider.java
+++ b/solr/core/src/java/org/apache/solr/security/PermissionNameProvider.java
@@ -46,6 +46,7 @@ public interface PermissionNameProvider {
     SCHEMA_EDIT_PERM("schema-edit", "*"),
     SECURITY_EDIT_PERM("security-edit", null),
     SECURITY_READ_PERM("security-read", null),
+    METRICS_READ_PERM("metrics-read", null),
     ALL("all", unmodifiableSet(new HashSet<>(asList("*", null))))
     ;
     final String name;

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8bbdb624/solr/core/src/java/org/apache/solr/servlet/SolrDispatchFilter.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/servlet/SolrDispatchFilter.java b/solr/core/src/java/org/apache/solr/servlet/SolrDispatchFilter.java
index e8c4657..dbc4b35 100644
--- a/solr/core/src/java/org/apache/solr/servlet/SolrDispatchFilter.java
+++ b/solr/core/src/java/org/apache/solr/servlet/SolrDispatchFilter.java
@@ -16,6 +16,7 @@
  */
 package org.apache.solr.servlet;
 
+import javax.management.MBeanServer;
 import javax.servlet.FilterChain;
 import javax.servlet.FilterConfig;
 import javax.servlet.ServletException;
@@ -33,6 +34,7 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.lang.invoke.MethodHandles;
+import java.lang.management.ManagementFactory;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.time.Instant;
@@ -45,6 +47,12 @@ import java.util.concurrent.atomic.AtomicReference;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
+import com.codahale.metrics.jvm.BufferPoolMetricSet;
+import com.codahale.metrics.jvm.ClassLoadingGaugeSet;
+import com.codahale.metrics.jvm.FileDescriptorRatioGauge;
+import com.codahale.metrics.jvm.GarbageCollectorMetricSet;
+import com.codahale.metrics.jvm.MemoryUsageGaugeSet;
+import com.codahale.metrics.jvm.ThreadStatesGaugeSet;
 import org.apache.commons.io.FileCleaningTracker;
 import org.apache.commons.io.input.CloseShieldInputStream;
 import org.apache.commons.io.output.CloseShieldOutputStream;
@@ -58,8 +66,10 @@ import org.apache.solr.common.util.ExecutorUtil;
 import org.apache.solr.core.CoreContainer;
 import org.apache.solr.core.NodeConfig;
 import org.apache.solr.core.SolrCore;
+import org.apache.solr.core.SolrInfoMBean;
 import org.apache.solr.core.SolrResourceLoader;
 import org.apache.solr.core.SolrXmlConfig;
+import org.apache.solr.metrics.SolrMetricManager;
 import org.apache.solr.request.SolrRequestInfo;
 import org.apache.solr.security.AuthenticationPlugin;
 import org.apache.solr.security.PKIAuthenticationPlugin;
@@ -157,6 +167,7 @@ public class SolrDispatchFilter extends BaseSolrFilter {
       this.cores = createCoreContainer(solrHome == null ? SolrResourceLoader.locateSolrHome() : Paths.get(solrHome),
                                        extraProperties);
       this.httpClient = cores.getUpdateShardHandler().getHttpClient();
+      setupJvmMetrics();
       log.debug("user.dir=" + System.getProperty("user.dir"));
     }
     catch( Throwable t ) {
@@ -171,6 +182,22 @@ public class SolrDispatchFilter extends BaseSolrFilter {
     log.trace("SolrDispatchFilter.init() done");
   }
 
+  private void setupJvmMetrics()  {
+    MBeanServer platformMBeanServer = ManagementFactory.getPlatformMBeanServer();
+    SolrMetricManager metricManager = cores.getMetricManager();
+    try {
+      String registry = SolrMetricManager.getRegistryName(SolrInfoMBean.Group.jvm);
+      metricManager.registerAll(registry, new BufferPoolMetricSet(platformMBeanServer), true, "bufferPools");
+      metricManager.registerAll(registry, new ClassLoadingGaugeSet(), true, "classLoading");
+      metricManager.register(registry, new FileDescriptorRatioGauge(), true, "fileDescriptorRatio");
+      metricManager.registerAll(registry, new GarbageCollectorMetricSet(), true, "gc");
+      metricManager.registerAll(registry, new MemoryUsageGaugeSet(), true, "memory");
+      metricManager.registerAll(registry, new ThreadStatesGaugeSet(), true, "threads"); // todo should we use CachedThreadStatesGaugeSet instead?
+    } catch (Exception e) {
+      log.warn("Error registering JVM metrics", e);
+    }
+  }
+
   private void logWelcomeBanner() {
     log.info(" ___      _       Welcome to Apache Solr\u2122 version {}", solrVersion());
     log.info("/ __| ___| |_ _   Starting in {} mode on port {}", isCloudMode() ? "cloud" : "standalone", getSolrPort());

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8bbdb624/solr/core/src/java/org/apache/solr/util/JmxUtil.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/util/JmxUtil.java b/solr/core/src/java/org/apache/solr/util/JmxUtil.java
new file mode 100644
index 0000000..02a070d
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/util/JmxUtil.java
@@ -0,0 +1,78 @@
+/*
+ * 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.solr.util;
+
+import javax.management.MBeanServer;
+import javax.management.MBeanServerFactory;
+import javax.management.remote.JMXConnectorServer;
+import javax.management.remote.JMXConnectorServerFactory;
+import javax.management.remote.JMXServiceURL;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * Utility methods to find a MBeanServer.
+ *
+ * This was factored out from {@link org.apache.solr.core.JmxMonitoredMap}
+ * and can eventually replace the logic used there.
+ */
+public final class JmxUtil {
+
+  /**
+   * Retrieve the first MBeanServer found.
+   *
+   * @return the first MBeanServer found
+   */
+  public static MBeanServer findFirstMBeanServer() {
+    return findMBeanServerForAgentId(null);
+  }
+
+  /**
+   * Find a MBeanServer given a service url.
+   *
+   * @param serviceUrl the service url
+   * @return a MBeanServer
+   */
+  public static MBeanServer findMBeanServerForServiceUrl(String serviceUrl) throws IOException {
+    if (serviceUrl == null) {
+      return null;
+    }
+
+    MBeanServer server = MBeanServerFactory.newMBeanServer();
+    JMXConnectorServer connector = JMXConnectorServerFactory
+        .newJMXConnectorServer(new JMXServiceURL(serviceUrl), null, server);
+    connector.start();
+
+    return server;
+  }
+
+  /**
+   * Find a MBeanServer given an agent id.
+   *
+   * @param agentId the agent id
+   * @return a MBeanServer
+   */
+  public static MBeanServer findMBeanServerForAgentId(String agentId) {
+    List<MBeanServer> servers = MBeanServerFactory.findMBeanServer(agentId);
+    if (servers == null || servers.isEmpty()) {
+      return null;
+    }
+
+    return servers.get(0);
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8bbdb624/solr/core/src/java/org/apache/solr/util/stats/MetricUtils.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/util/stats/MetricUtils.java b/solr/core/src/java/org/apache/solr/util/stats/MetricUtils.java
new file mode 100644
index 0000000..62f5776
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/util/stats/MetricUtils.java
@@ -0,0 +1,144 @@
+/*
+ * 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.solr.util.stats;
+
+import java.util.List;
+import java.util.Map;
+import java.util.SortedSet;
+import java.util.concurrent.TimeUnit;
+
+import com.codahale.metrics.Counter;
+import com.codahale.metrics.Gauge;
+import com.codahale.metrics.Histogram;
+import com.codahale.metrics.Meter;
+import com.codahale.metrics.Metric;
+import com.codahale.metrics.MetricFilter;
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.Snapshot;
+import com.codahale.metrics.Timer;
+import org.apache.solr.common.util.NamedList;
+
+/**
+ * Metrics specific utility functions.
+ */
+public class MetricUtils {
+
+  /**
+   * Adds metrics from a Timer to a NamedList, using well-known names.
+   * @param lst The NamedList to add the metrics data to
+   * @param timer The Timer to extract the metrics from
+   */
+  public static void addMetrics(NamedList<Object> lst, Timer timer) {
+    Snapshot snapshot = timer.getSnapshot();
+    lst.add("avgRequestsPerSecond", timer.getMeanRate());
+    lst.add("5minRateRequestsPerSecond", timer.getFiveMinuteRate());
+    lst.add("15minRateRequestsPerSecond", timer.getFifteenMinuteRate());
+    lst.add("avgTimePerRequest", nsToMs(snapshot.getMean()));
+    lst.add("medianRequestTime", nsToMs(snapshot.getMedian()));
+    lst.add("75thPcRequestTime", nsToMs(snapshot.get75thPercentile()));
+    lst.add("95thPcRequestTime", nsToMs(snapshot.get95thPercentile()));
+    lst.add("99thPcRequestTime", nsToMs(snapshot.get99thPercentile()));
+    lst.add("999thPcRequestTime", nsToMs(snapshot.get999thPercentile()));
+  }
+
+  /**
+   * Converts a double representing nanoseconds to a double representing milliseconds.
+   *
+   * @param ns the amount of time in nanoseconds
+   * @return the amount of time in milliseconds
+   */
+  static double nsToMs(double ns) {
+    return ns / TimeUnit.MILLISECONDS.toNanos(1);
+  }
+
+  /**
+   * Returns a NamedList respresentation of the given metric registry. Only those metrics
+   * are converted to NamedList which match at least one of the given MetricFilter instances.
+   *
+   * @param registry      the {@link MetricRegistry} to be converted to NamedList
+   * @param metricFilters a list of {@link MetricFilter} instances
+   * @return a {@link NamedList}
+   */
+  public static NamedList toNamedList(MetricRegistry registry, List<MetricFilter> metricFilters) {
+    NamedList response = new NamedList();
+    Map<String, Metric> metrics = registry.getMetrics();
+    SortedSet<String> names = registry.getNames();
+    names.stream().filter(s -> metricFilters.stream().anyMatch(metricFilter -> metricFilter.matches(s, metrics.get(s)))).forEach(n -> {
+      Metric metric = metrics.get(n);
+      if (metric instanceof Counter) {
+        Counter counter = (Counter) metric;
+        response.add(n, counterToNamedList(counter));
+      } else if (metric instanceof Gauge) {
+        Gauge gauge = (Gauge) metric;
+        response.add(n, gaugeToNamedList(gauge));
+      } else if (metric instanceof Meter) {
+        Meter meter = (Meter) metric;
+        response.add(n, meterToNamedList(meter));
+      } else if (metric instanceof Timer) {
+        Timer timer = (Timer) metric;
+        response.add(n, timerToNamedList(timer));
+      } else if (metric instanceof Histogram) {
+        Histogram histogram = (Histogram) metric;
+        response.add(n, histogramToNamedList(histogram));
+      }
+    });
+    return response;
+  }
+
+  static NamedList histogramToNamedList(Histogram histogram) {
+    NamedList response = new NamedList();
+    Snapshot snapshot = histogram.getSnapshot();
+    response.add("requests", histogram.getCount());
+    response.add("minTime", nsToMs(snapshot.getMin()));
+    response.add("maxTime", nsToMs(snapshot.getMax()));
+    response.add("avgTimePerRequest", nsToMs(snapshot.getMean()));
+    response.add("medianRequestTime", nsToMs(snapshot.getMedian()));
+    response.add("75thPcRequestTime", nsToMs(snapshot.get75thPercentile()));
+    response.add("95thPcRequestTime", nsToMs(snapshot.get95thPercentile()));
+    response.add("99thPcRequestTime", nsToMs(snapshot.get99thPercentile()));
+    response.add("999thPcRequestTime", nsToMs(snapshot.get999thPercentile()));
+    return response;
+  }
+
+  static NamedList timerToNamedList(Timer timer) {
+    NamedList response = new NamedList();
+    addMetrics(response, timer);
+    return response;
+  }
+
+  static NamedList meterToNamedList(Meter meter) {
+    NamedList response = new NamedList();
+    response.add("requests", meter.getCount());
+    response.add("avgRequestsPerSecond", meter.getMeanRate());
+    response.add("1minRateRequestsPerSecond", meter.getOneMinuteRate());
+    response.add("5minRateRequestsPerSecond", meter.getFiveMinuteRate());
+    response.add("15minRateRequestsPerSecond", meter.getFifteenMinuteRate());
+    return response;
+  }
+
+  static NamedList gaugeToNamedList(Gauge gauge) {
+    NamedList response = new NamedList();
+    response.add("value", gauge.getValue());
+    return response;
+  }
+
+  static NamedList counterToNamedList(Counter counter) {
+    NamedList response = new NamedList();
+    response.add("requests", counter.getCount());
+    return response;
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8bbdb624/solr/core/src/java/org/apache/solr/util/stats/TimerUtils.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/util/stats/TimerUtils.java b/solr/core/src/java/org/apache/solr/util/stats/TimerUtils.java
deleted file mode 100644
index 243c1ee..0000000
--- a/solr/core/src/java/org/apache/solr/util/stats/TimerUtils.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * 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.solr.util.stats;
-
-import java.util.concurrent.TimeUnit;
-
-import com.codahale.metrics.Snapshot;
-import com.codahale.metrics.Timer;
-import org.apache.solr.common.util.NamedList;
-
-/**
- * Solr specific {@link Timer} utility functions.
- */
-public class TimerUtils {
-
-  /**
-   * Adds metrics from a Timer to a NamedList, using well-known names.
-   * @param lst The NamedList to add the metrics data to
-   * @param timer The Timer to extract the metrics from
-   */
-  public static void addMetrics(NamedList<Object> lst, Timer timer) {
-    Snapshot snapshot = timer.getSnapshot();
-    lst.add("avgRequestsPerSecond", timer.getMeanRate());
-    lst.add("5minRateRequestsPerSecond", timer.getFiveMinuteRate());
-    lst.add("15minRateRequestsPerSecond", timer.getFifteenMinuteRate());
-    lst.add("avgTimePerRequest", nsToMs(snapshot.getMean()));
-    lst.add("medianRequestTime", nsToMs(snapshot.getMedian()));
-    lst.add("75thPcRequestTime", nsToMs(snapshot.get75thPercentile()));
-    lst.add("95thPcRequestTime", nsToMs(snapshot.get95thPercentile()));
-    lst.add("99thPcRequestTime", nsToMs(snapshot.get99thPercentile()));
-    lst.add("999thPcRequestTime", nsToMs(snapshot.get999thPercentile()));
-  }
-
-  /**
-   * Converts a double representing nanoseconds to a double representing milliseconds.
-   *
-   * @param ns the amount of time in nanoseconds
-   * @return the amount of time in milliseconds
-   */
-  static double nsToMs(double ns) {
-    return ns / TimeUnit.MILLISECONDS.toNanos(1);
-  }
-
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8bbdb624/solr/core/src/test-files/solr/solr-metricreporter.xml
----------------------------------------------------------------------
diff --git a/solr/core/src/test-files/solr/solr-metricreporter.xml b/solr/core/src/test-files/solr/solr-metricreporter.xml
new file mode 100644
index 0000000..cd8d737
--- /dev/null
+++ b/solr/core/src/test-files/solr/solr-metricreporter.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!--
+ 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.
+-->
+
+<solr>
+ <metrics>
+  <!-- this reporter doesn't specify 'group' or 'registry', it will be instantiated for any group. -->
+  <reporter name="universal" class="org.apache.solr.metrics.reporters.MockMetricReporter">
+    <str name="configurable">configured</str>
+  </reporter>
+  <!-- this reporter will be created for both "node" and "core" groups -->
+  <reporter name="multigroup" group="node, core" class="org.apache.solr.metrics.reporters.MockMetricReporter">
+    <str name="configurable">configured</str>
+  </reporter>
+  <!-- this reporter will be created for both "node" and "core.collection1" registries -->
+  <reporter name="multiregistry" registry="node, core.collection1" class="org.apache.solr.metrics.reporters.MockMetricReporter">
+    <str name="configurable">configured</str>
+  </reporter>
+  <reporter name="reporter1" group="jvm" class="org.apache.solr.metrics.reporters.MockMetricReporter">
+    <str name="configurable">configured</str>
+  </reporter>
+  <reporter name="reporter1" group="jetty" class="org.apache.solr.metrics.reporters.MockMetricReporter">
+    <str name="configurable">configured</str>
+  </reporter>
+  <reporter name="reporter1" group="http" class="org.apache.solr.metrics.reporters.MockMetricReporter">
+    <str name="configurable">configured</str>
+  </reporter>
+  <reporter name="reporter1" group="node" class="org.apache.solr.metrics.reporters.MockMetricReporter">
+    <str name="configurable">configured</str>
+  </reporter>
+  <!-- core reporters. -->
+  <reporter name="reporter1" group="core" class="org.apache.solr.metrics.reporters.MockMetricReporter">
+    <str name="configurable">configured</str>
+  </reporter>
+  <reporter name="reporter2" group="core" class="org.apache.solr.metrics.reporters.MockMetricReporter">
+    <str name="configurable">configured</str>
+  </reporter>
+  <!-- reporter for a specific registry -->
+  <reporter name="specific" registry="core.collection1" class="org.apache.solr.metrics.reporters.MockMetricReporter">
+    <str name="configurable">configured</str>
+  </reporter>
+ </metrics>
+</solr>

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8bbdb624/solr/core/src/test/org/apache/solr/handler/admin/MetricsHandlerTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/handler/admin/MetricsHandlerTest.java b/solr/core/src/test/org/apache/solr/handler/admin/MetricsHandlerTest.java
new file mode 100644
index 0000000..3667285
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/handler/admin/MetricsHandlerTest.java
@@ -0,0 +1,97 @@
+/*
+ * 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.solr.handler.admin;
+
+import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.common.params.CommonParams;
+import org.apache.solr.common.util.NamedList;
+import org.apache.solr.response.SolrQueryResponse;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * Test for {@link MetricsHandler}
+ */
+public class MetricsHandlerTest extends SolrTestCaseJ4 {
+  @BeforeClass
+  public static void beforeClass() throws Exception {
+    initCore("solrconfig.xml", "schema.xml");
+  }
+
+  @Test
+  public void test() throws Exception {
+    MetricsHandler handler = new MetricsHandler(h.getCoreContainer());
+
+    SolrQueryResponse resp = new SolrQueryResponse();
+    handler.handleRequestBody(req(CommonParams.QT, "/admin/metrics", CommonParams.WT, "json"), resp);
+    NamedList values = resp.getValues();
+    assertNotNull(values.get("metrics"));
+    values = (NamedList) values.get("metrics");
+    System.out.println(values);
+    assertNotNull(values.get("solr.jetty"));
+    assertNotNull(values.get("solr.jvm"));
+    assertNotNull(values.get("solr.http"));
+    assertNotNull(values.get("solr.node"));
+    NamedList nl = (NamedList) values.get("solr.core.collection1");
+    assertNotNull(nl);
+    assertNotNull(nl.get("newSearcherErrors")); // counter type
+    assertNotNull(((NamedList) nl.get("newSearcherErrors")).get("requests"));
+    assertEquals(0L, ((NamedList) nl.get("newSearcherErrors")).get("requests"));
+    nl = (NamedList) values.get("solr.node");
+    assertNotNull(nl.get("cores.loaded")); // int gauge
+    assertEquals(1, ((NamedList) nl.get("cores.loaded")).get("value"));
+    assertNotNull(nl.get("QUERYHANDLER./admin/authorization.clientErrors")); // timer type
+    assertEquals(5, ((NamedList) nl.get("QUERYHANDLER./admin/authorization.clientErrors")).size());
+
+    resp = new SolrQueryResponse();
+    handler.handleRequestBody(req(CommonParams.QT, "/admin/metrics", CommonParams.WT, "json", "group", "jvm,jetty"), resp);
+    values = resp.getValues();
+    assertNotNull(values.get("metrics"));
+    values = (NamedList) values.get("metrics");
+    assertEquals(2, values.size());
+    assertNotNull(values.get("solr.jetty"));
+    assertNotNull(values.get("solr.jvm"));
+
+    resp = new SolrQueryResponse();
+    handler.handleRequestBody(req(CommonParams.QT, "/admin/metrics", CommonParams.WT, "json", "group", "jvm,jetty"), resp);
+    values = resp.getValues();
+    assertNotNull(values.get("metrics"));
+    values = (NamedList) values.get("metrics");
+    assertEquals(2, values.size());
+    assertNotNull(values.get("solr.jetty"));
+    assertNotNull(values.get("solr.jvm"));
+
+    resp = new SolrQueryResponse();
+    handler.handleRequestBody(req(CommonParams.QT, "/admin/metrics", CommonParams.WT, "json", "group", "jvm", "group", "jetty"), resp);
+    values = resp.getValues();
+    assertNotNull(values.get("metrics"));
+    values = (NamedList) values.get("metrics");
+    assertEquals(2, values.size());
+    assertNotNull(values.get("solr.jetty"));
+    assertNotNull(values.get("solr.jvm"));
+
+    resp = new SolrQueryResponse();
+    handler.handleRequestBody(req(CommonParams.QT, "/admin/metrics", CommonParams.WT, "json", "group", "node", "type", "counter"), resp);
+    values = resp.getValues();
+    assertNotNull(values.get("metrics"));
+    values = (NamedList) values.get("metrics");
+    assertEquals(1, values.size());
+    assertNotNull(values.get("solr.node"));
+    assertNull(values.get("QUERYHANDLER./admin/authorization.errors")); // this is a timer node
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8bbdb624/solr/core/src/test/org/apache/solr/metrics/SolrCoreMetricManagerTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/metrics/SolrCoreMetricManagerTest.java b/solr/core/src/test/org/apache/solr/metrics/SolrCoreMetricManagerTest.java
new file mode 100644
index 0000000..65ffb93
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/metrics/SolrCoreMetricManagerTest.java
@@ -0,0 +1,172 @@
+/*
+ * 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.solr.metrics;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Random;
+import java.util.stream.Collectors;
+
+import com.codahale.metrics.Counter;
+import com.codahale.metrics.Metric;
+import com.codahale.metrics.MetricRegistry;
+import org.apache.lucene.util.TestUtil;
+import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.common.params.CoreAdminParams;
+import org.apache.solr.core.PluginInfo;
+import org.apache.solr.core.SolrInfoMBean;
+import org.apache.solr.metrics.reporters.MockMetricReporter;
+import org.apache.solr.schema.FieldType;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class SolrCoreMetricManagerTest extends SolrTestCaseJ4 {
+  private static final int MAX_ITERATIONS = 100;
+
+  private SolrCoreMetricManager coreMetricManager;
+  private SolrMetricManager metricManager;
+
+  @Before
+  public void beforeTest() throws Exception {
+    initCore("solrconfig-basic.xml", "schema.xml");
+    coreMetricManager = h.getCore().getCoreMetricManager();
+    metricManager = h.getCore().getCoreDescriptor().getCoreContainer().getMetricManager();
+  }
+
+  @After
+  public void afterTest() throws IOException {
+    coreMetricManager.close();
+    assertTrue(metricManager.getReporters(coreMetricManager.getRegistryName()).isEmpty());
+    deleteCore();
+  }
+
+  @Test
+  public void testRegisterMetrics() {
+    Random random = random();
+
+    String scope = SolrMetricTestUtils.getRandomScope(random);
+    SolrInfoMBean.Category category = SolrMetricTestUtils.getRandomCategory(random);
+    Map<String, Counter> metrics = SolrMetricTestUtils.getRandomMetrics(random);
+    SolrMetricProducer producer = SolrMetricTestUtils.getProducerOf(metricManager, category, scope, metrics);
+    try {
+      coreMetricManager.registerMetricProducer(scope, producer);
+      assertNotNull(scope);
+      assertNotNull(category);
+      assertNotNull(metrics);
+      assertRegistered(scope, metrics, coreMetricManager);
+    } catch (final IllegalArgumentException e) {
+      assertTrue("expected at least one null but got: scope="+scope+" category="+category+" metrics="+metrics,
+          (scope == null || category == null || metrics == null));
+      assertRegistered(scope, new HashMap<>(), coreMetricManager);
+    }
+  }
+
+  @Test
+  public void testRegisterMetricsWithReplacements() {
+    Random random = random();
+
+    Map<String, Counter> registered = new HashMap<>();
+    String scope = SolrMetricTestUtils.getRandomScope(random, true);
+    SolrInfoMBean.Category category = SolrMetricTestUtils.getRandomCategory(random, true);
+
+    int iterations = TestUtil.nextInt(random, 0, MAX_ITERATIONS);
+    for (int i = 0; i < iterations; ++i) {
+      Map<String, Counter> metrics = SolrMetricTestUtils.getRandomMetricsWithReplacements(random, registered);
+      if (metrics.isEmpty()) {
+        continue;
+      }
+      SolrMetricProducer producer = SolrMetricTestUtils.getProducerOf(metricManager, category, scope, metrics);
+      coreMetricManager.registerMetricProducer(scope, producer);
+      registered.putAll(metrics);
+      assertRegistered(scope, registered, coreMetricManager);
+    }
+  }
+
+  @Test
+  public void testLoadReporter() throws Exception {
+    Random random = random();
+
+    String className = MockMetricReporter.class.getName();
+    String reporterName = TestUtil.randomUnicodeString(random);
+
+    Map<String, Object> attrs = new HashMap<>();
+    attrs.put(FieldType.CLASS_NAME, className);
+    attrs.put(CoreAdminParams.NAME, reporterName);
+
+    boolean shouldDefineConfigurable = random.nextBoolean();
+    String configurable = TestUtil.randomUnicodeString(random);
+    if (shouldDefineConfigurable) attrs.put("configurable", configurable);
+
+    boolean shouldDefinePlugin = random.nextBoolean();
+    PluginInfo pluginInfo = shouldDefinePlugin ? new PluginInfo(TestUtil.randomUnicodeString(random), attrs) : null;
+
+    try {
+      metricManager.loadReporter(coreMetricManager.getRegistryName(), coreMetricManager.getCore().getResourceLoader(), pluginInfo);
+      assertNotNull(pluginInfo);
+      Map<String, SolrMetricReporter> reporters = metricManager.getReporters(coreMetricManager.getRegistryName());
+      assertTrue("reporters.size should be > 0, but was + " + reporters.size(), reporters.size() > 0);
+      assertNotNull("reporter " + reporterName + " not present among " + reporters, reporters.get(reporterName));
+      assertTrue("wrong reporter class: " + reporters.get(reporterName), reporters.get(reporterName) instanceof MockMetricReporter);
+    } catch (IllegalArgumentException e) {
+      assertTrue(pluginInfo == null || attrs.get("configurable") == null);
+      assertNull(metricManager.getReporters(coreMetricManager.getRegistryName()).get(reporterName));
+    }
+  }
+
+  private void assertRegistered(String scope, Map<String, Counter> newMetrics, SolrCoreMetricManager coreMetricManager) {
+    if (scope == null) {
+      return;
+    }
+    String filter = "." + scope + ".";
+    MetricRegistry registry = metricManager.registry(coreMetricManager.getRegistryName());
+    assertEquals(newMetrics.size(), registry.getMetrics().
+        keySet().stream().filter(s -> s.contains(filter)).count());
+
+    Map<String, Metric> registeredMetrics = registry.getMetrics().
+        entrySet().stream().filter(e -> e.getKey() != null && e.getKey().contains(filter)).
+        collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue()));
+    for (Map.Entry<String, Metric> entry : registeredMetrics.entrySet()) {
+      String name = entry.getKey();
+      Metric expectedMetric = entry.getValue();
+
+      Metric actualMetric = registry.getMetrics().get(name);
+
+      assertNotNull(actualMetric);
+      assertEquals(expectedMetric, actualMetric);
+    }
+  }
+
+  @Test
+  public void testRegistryName() throws Exception {
+    String collectionName = "my_collection_";
+    String cloudCoreName = "my_collection__shard1_0_replica0";
+    String simpleCoreName = "collection_1_replica0";
+    String simpleRegistryName = "solr.core." + simpleCoreName;
+    String cloudRegistryName = "solr.core." + cloudCoreName;
+    String nestedRegistryName = "solr.core.my_collection_.shard1_0.replica0";
+    // pass through
+    assertEquals(cloudRegistryName, coreMetricManager.createRegistryName(null, cloudCoreName));
+    assertEquals(simpleRegistryName, coreMetricManager.createRegistryName(null, simpleCoreName));
+    // unknown naming scheme -> pass through
+    assertEquals(simpleRegistryName, coreMetricManager.createRegistryName(collectionName, simpleCoreName));
+    // cloud collection
+    assertEquals(nestedRegistryName, coreMetricManager.createRegistryName(collectionName, cloudCoreName));
+
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8bbdb624/solr/core/src/test/org/apache/solr/metrics/SolrMetricManagerTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/metrics/SolrMetricManagerTest.java b/solr/core/src/test/org/apache/solr/metrics/SolrMetricManagerTest.java
new file mode 100644
index 0000000..ecddfba
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/metrics/SolrMetricManagerTest.java
@@ -0,0 +1,273 @@
+/*
+ * 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.solr.metrics;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Random;
+import java.util.Set;
+
+import com.codahale.metrics.Counter;
+import com.codahale.metrics.Metric;
+import com.codahale.metrics.MetricRegistry;
+import org.apache.lucene.util.TestUtil;
+import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.common.util.NamedList;
+import org.apache.solr.core.PluginInfo;
+import org.apache.solr.core.SolrInfoMBean;
+import org.apache.solr.core.SolrResourceLoader;
+import org.apache.solr.metrics.reporters.MockMetricReporter;
+import org.junit.Test;
+
+public class SolrMetricManagerTest extends SolrTestCaseJ4 {
+
+  @Test
+  public void testOverridableRegistryName() throws Exception {
+    Random r = random();
+    String originalName = TestUtil.randomSimpleString(r, 1, 10);
+    String targetName = TestUtil.randomSimpleString(r, 1, 10);
+    // no override
+    String result = SolrMetricManager.overridableRegistryName(originalName);
+    assertEquals(SolrMetricManager.REGISTRY_NAME_PREFIX + originalName, result);
+    // with override
+    System.setProperty(SolrMetricManager.REGISTRY_NAME_PREFIX + originalName, targetName);
+    result = SolrMetricManager.overridableRegistryName(originalName);
+    assertEquals(SolrMetricManager.REGISTRY_NAME_PREFIX + targetName, result);
+  }
+
+  @Test
+  public void testMoveMetrics() throws Exception {
+    Random r = random();
+
+    SolrMetricManager metricManager = new SolrMetricManager();
+
+    Map<String, Counter> metrics1 = SolrMetricTestUtils.getRandomMetrics(r, true);
+    Map<String, Counter> metrics2 = SolrMetricTestUtils.getRandomMetrics(r, true);
+    String fromName = TestUtil.randomSimpleString(r, 1, 10);
+    String toName = TestUtil.randomSimpleString(r, 1, 10);
+    // register test metrics
+    for (Map.Entry<String, Counter> entry : metrics1.entrySet()) {
+      metricManager.register(fromName, entry.getValue(), false, entry.getKey(), "metrics1");
+    }
+    for (Map.Entry<String, Counter> entry : metrics2.entrySet()) {
+      metricManager.register(fromName, entry.getValue(), false, entry.getKey(), "metrics2");
+    }
+    assertEquals(metrics1.size() + metrics2.size(), metricManager.registry(fromName).getMetrics().size());
+
+    // move metrics1
+    metricManager.moveMetrics(fromName, toName, new SolrMetricManager.PrefixFilter("metrics1"));
+    // check the remaining metrics
+    Map<String, Metric> fromMetrics = metricManager.registry(fromName).getMetrics();
+    assertEquals(metrics2.size(), fromMetrics.size());
+    for (Map.Entry<String, Counter> entry : metrics2.entrySet()) {
+      Object value = fromMetrics.get(SolrMetricManager.mkName(entry.getKey(), "metrics2"));
+      assertNotNull(value);
+      assertEquals(entry.getValue(), value);
+    }
+    // check the moved metrics
+    Map<String, Metric> toMetrics = metricManager.registry(toName).getMetrics();
+    assertEquals(metrics1.size(), toMetrics.size());
+    for (Map.Entry<String, Counter> entry : metrics1.entrySet()) {
+      Object value = toMetrics.get(SolrMetricManager.mkName(entry.getKey(), "metrics1"));
+      assertNotNull(value);
+      assertEquals(entry.getValue(), value);
+    }
+
+    // move all remaining metrics
+    metricManager.moveMetrics(fromName, toName, null);
+    fromMetrics = metricManager.registry(fromName).getMetrics();
+    assertEquals(0, fromMetrics.size());
+    toMetrics = metricManager.registry(toName).getMetrics();
+    assertEquals(metrics1.size() + metrics2.size(), toMetrics.size());
+  }
+
+  @Test
+  public void testRegisterAll() throws Exception {
+    Random r = random();
+
+    SolrMetricManager metricManager = new SolrMetricManager();
+
+    Map<String, Counter> metrics = SolrMetricTestUtils.getRandomMetrics(r, true);
+    MetricRegistry mr = new MetricRegistry();
+    for (Map.Entry<String, Counter> entry : metrics.entrySet()) {
+      mr.register(entry.getKey(), entry.getValue());
+    }
+
+    String registryName = TestUtil.randomSimpleString(r, 1, 10);
+    assertEquals(0, metricManager.registry(registryName).getMetrics().size());
+    metricManager.registerAll(registryName, mr, false);
+    // this should simply skip existing names
+    metricManager.registerAll(registryName, mr, true);
+    // this should produce error
+    try {
+      metricManager.registerAll(registryName, mr, false);
+      fail("registerAll with duplicate metric names should fail");
+    } catch (IllegalArgumentException e) {
+      // expected
+    }
+  }
+
+  @Test
+  public void testClearMetrics() throws Exception {
+    Random r = random();
+
+    SolrMetricManager metricManager = new SolrMetricManager();
+
+    Map<String, Counter> metrics = SolrMetricTestUtils.getRandomMetrics(r, true);
+    String registryName = TestUtil.randomSimpleString(r, 1, 10);
+
+    for (Map.Entry<String, Counter> entry : metrics.entrySet()) {
+      metricManager.register(registryName, entry.getValue(), false, entry.getKey(), "foo", "bar");
+    }
+    for (Map.Entry<String, Counter> entry : metrics.entrySet()) {
+      metricManager.register(registryName, entry.getValue(), false, entry.getKey(), "foo", "baz");
+    }
+    for (Map.Entry<String, Counter> entry : metrics.entrySet()) {
+      metricManager.register(registryName, entry.getValue(), false, entry.getKey(), "foo");
+    }
+
+    assertEquals(metrics.size() * 3, metricManager.registry(registryName).getMetrics().size());
+
+    // clear "foo.bar"
+    Set<String> removed = metricManager.clearMetrics(registryName, "foo", "bar");
+    assertEquals(metrics.size(), removed.size());
+    for (String s : removed) {
+      assertTrue(s.startsWith("foo.bar."));
+    }
+    removed = metricManager.clearMetrics(registryName, "foo", "baz");
+    assertEquals(metrics.size(), removed.size());
+    for (String s : removed) {
+      assertTrue(s.startsWith("foo.baz."));
+    }
+    // perhaps surprisingly, this works too - see PrefixFilter docs
+    removed = metricManager.clearMetrics(registryName, "fo");
+    assertEquals(metrics.size(), removed.size());
+    for (String s : removed) {
+      assertTrue(s.startsWith("foo."));
+    }
+  }
+
+  @Test
+  public void testSimpleMetrics() throws Exception {
+    Random r = random();
+
+    SolrMetricManager metricManager = new SolrMetricManager();
+
+    String registryName = TestUtil.randomSimpleString(r, 1, 10);
+
+    metricManager.counter(registryName, "simple_counter", "foo", "bar");
+    metricManager.timer(registryName, "simple_timer", "foo", "bar");
+    metricManager.meter(registryName, "simple_meter", "foo", "bar");
+    metricManager.histogram(registryName, "simple_histogram", "foo", "bar");
+    Map<String, Metric> metrics = metricManager.registry(registryName).getMetrics();
+    assertEquals(4, metrics.size());
+    for (Map.Entry<String, Metric> entry : metrics.entrySet()) {
+      assertTrue(entry.getKey().startsWith("foo.bar.simple_"));
+    }
+  }
+
+  @Test
+  public void testRegistryName() throws Exception {
+    Random r = random();
+
+    String name = TestUtil.randomSimpleString(r, 1, 10);
+
+    String result = SolrMetricManager.getRegistryName(SolrInfoMBean.Group.core, name, "collection1");
+    assertEquals("solr.core." + name + ".collection1", result);
+    // try it with already prefixed name - group will be ignored
+    result = SolrMetricManager.getRegistryName(SolrInfoMBean.Group.core, result);
+    assertEquals("solr.core." + name + ".collection1", result);
+    // try it with already prefixed name but with additional segments
+    result = SolrMetricManager.getRegistryName(SolrInfoMBean.Group.core, result, "shard1", "replica1");
+    assertEquals("solr.core." + name + ".collection1.shard1.replica1", result);
+  }
+
+  @Test
+  public void testReporters() throws Exception {
+    Random r = random();
+
+    SolrResourceLoader loader = new SolrResourceLoader();
+    SolrMetricManager metricManager = new SolrMetricManager();
+
+    PluginInfo[] plugins = new PluginInfo[] {
+        createPluginInfo("universal_foo", null, null),
+        createPluginInfo("multigroup_foo", "jvm, node, core", null),
+        createPluginInfo("multiregistry_foo", null, "solr.node, solr.core.collection1"),
+        createPluginInfo("specific_foo", null, "solr.core.collection1"),
+        createPluginInfo("node_foo", "node", null),
+        createPluginInfo("core_foo", "core", null)
+    };
+
+    metricManager.loadReporters(plugins, loader, SolrInfoMBean.Group.node);
+    Map<String, SolrMetricReporter> reporters = metricManager.getReporters(
+        SolrMetricManager.getRegistryName(SolrInfoMBean.Group.node));
+    assertEquals(4, reporters.size());
+    assertTrue(reporters.containsKey("universal_foo"));
+    assertTrue(reporters.containsKey("multigroup_foo"));
+    assertTrue(reporters.containsKey("node_foo"));
+    assertTrue(reporters.containsKey("multiregistry_foo"));
+
+    metricManager.loadReporters(plugins, loader, SolrInfoMBean.Group.core, "collection1");
+    reporters = metricManager.getReporters(
+        SolrMetricManager.getRegistryName(SolrInfoMBean.Group.core, "collection1"));
+    assertEquals(5, reporters.size());
+    assertTrue(reporters.containsKey("universal_foo"));
+    assertTrue(reporters.containsKey("multigroup_foo"));
+    assertTrue(reporters.containsKey("specific_foo"));
+    assertTrue(reporters.containsKey("core_foo"));
+    assertTrue(reporters.containsKey("multiregistry_foo"));
+
+    metricManager.loadReporters(plugins, loader, SolrInfoMBean.Group.jvm);
+    reporters = metricManager.getReporters(
+        SolrMetricManager.getRegistryName(SolrInfoMBean.Group.jvm));
+    assertEquals(2, reporters.size());
+    assertTrue(reporters.containsKey("universal_foo"));
+    assertTrue(reporters.containsKey("multigroup_foo"));
+
+    metricManager.removeRegistry("solr.jvm");
+    reporters = metricManager.getReporters(
+        SolrMetricManager.getRegistryName(SolrInfoMBean.Group.jvm));
+    assertEquals(0, reporters.size());
+
+    metricManager.removeRegistry("solr.node");
+    reporters = metricManager.getReporters(
+        SolrMetricManager.getRegistryName(SolrInfoMBean.Group.node));
+    assertEquals(0, reporters.size());
+
+    metricManager.removeRegistry("solr.core.collection1");
+    reporters = metricManager.getReporters(
+        SolrMetricManager.getRegistryName(SolrInfoMBean.Group.core, "collection1"));
+    assertEquals(0, reporters.size());
+
+  }
+
+  private PluginInfo createPluginInfo(String name, String group, String registry) {
+    Map<String,String> attrs = new HashMap<>();
+    attrs.put("name", name);
+    attrs.put("class", MockMetricReporter.class.getName());
+    if (group != null) {
+      attrs.put("group", group);
+    }
+    if (registry != null) {
+      attrs.put("registry", registry);
+    }
+    NamedList initArgs = new NamedList();
+    initArgs.add("configurable", "true");
+    return new PluginInfo("SolrMetricReporter", attrs, initArgs, null);
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8bbdb624/solr/core/src/test/org/apache/solr/metrics/SolrMetricReporterTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/metrics/SolrMetricReporterTest.java b/solr/core/src/test/org/apache/solr/metrics/SolrMetricReporterTest.java
new file mode 100644
index 0000000..b275919
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/metrics/SolrMetricReporterTest.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.solr.metrics;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Random;
+
+import org.apache.lucene.util.LuceneTestCase;
+import org.apache.lucene.util.TestUtil;
+import org.apache.solr.common.params.CoreAdminParams;
+import org.apache.solr.core.PluginInfo;
+import org.apache.solr.metrics.reporters.MockMetricReporter;
+import org.apache.solr.schema.FieldType;
+import org.junit.Test;
+
+public class SolrMetricReporterTest extends LuceneTestCase {
+
+  @Test
+  public void testInit() throws Exception {
+    Random random = random();
+
+    SolrMetricManager metricManager = new SolrMetricManager();
+
+    final String registryName = TestUtil.randomSimpleString(random);
+    final MockMetricReporter reporter = new MockMetricReporter(metricManager, registryName);
+
+    Map<String, Object> attrs = new HashMap<>();
+    attrs.put(FieldType.CLASS_NAME, MockMetricReporter.class.getName());
+    attrs.put(CoreAdminParams.NAME, TestUtil.randomUnicodeString(random));
+
+    boolean shouldDefineConfigurable = random.nextBoolean();
+    String configurable = TestUtil.randomUnicodeString(random);
+    if (shouldDefineConfigurable) attrs.put("configurable", configurable);
+
+    boolean shouldDefinePlugin = random.nextBoolean();
+    String type = TestUtil.randomUnicodeString(random);
+    PluginInfo pluginInfo = shouldDefinePlugin ? new PluginInfo(type, attrs) : null;
+
+    try {
+      reporter.init(pluginInfo);
+      assertNotNull(pluginInfo);
+      assertEquals(configurable, attrs.get("configurable"));
+      assertTrue(reporter.didValidate);
+      assertNotNull(reporter.configurable);
+      assertEquals(configurable, reporter.configurable);
+    } catch (IllegalStateException e) {
+      assertTrue(pluginInfo == null || attrs.get("configurable") == null);
+      assertTrue(reporter.didValidate);
+      assertNull(reporter.configurable);
+    } finally {
+      reporter.close();
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8bbdb624/solr/core/src/test/org/apache/solr/metrics/SolrMetricTestUtils.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/metrics/SolrMetricTestUtils.java b/solr/core/src/test/org/apache/solr/metrics/SolrMetricTestUtils.java
new file mode 100644
index 0000000..44ae9db
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/metrics/SolrMetricTestUtils.java
@@ -0,0 +1,140 @@
+/*
+ * 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.solr.metrics;
+
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Random;
+
+import com.codahale.metrics.Counter;
+import org.apache.lucene.util.TestUtil;
+import org.apache.solr.common.util.NamedList;
+import org.apache.solr.core.SolrInfoMBean;
+
+public final class SolrMetricTestUtils {
+
+  private static final int                    MAX_ITERATIONS = 100;
+  private static final SolrInfoMBean.Category CATEGORIES[]   = SolrInfoMBean.Category.values();
+
+  public static String getRandomScope(Random random) {
+    return getRandomScope(random, random.nextBoolean());
+  }
+
+  public static String getRandomScope(Random random, boolean shouldDefineScope) {
+    return shouldDefineScope ? TestUtil.randomSimpleString(random, 1, 10) : null; // must be simple string for JMX publishing
+  }
+
+  public static SolrInfoMBean.Category getRandomCategory(Random random) {
+    return getRandomCategory(random, random.nextBoolean());
+  }
+
+  public static SolrInfoMBean.Category getRandomCategory(Random random, boolean shouldDefineCategory) {
+    return shouldDefineCategory ? CATEGORIES[TestUtil.nextInt(random, 0, CATEGORIES.length - 1)] : null;
+  }
+
+  public static Map<String, Counter> getRandomMetrics(Random random) {
+    return getRandomMetrics(random, random.nextBoolean());
+  }
+
+  public static Map<String, Counter> getRandomMetrics(Random random, boolean shouldDefineMetrics) {
+    return shouldDefineMetrics ? getRandomMetricsWithReplacements(random, new HashMap<>()) : null;
+  }
+
+  public static final String SUFFIX = "_testing";
+
+  public static Map<String, Counter> getRandomMetricsWithReplacements(Random random, Map<String, Counter> existing) {
+    HashMap<String, Counter> metrics = new HashMap<>();
+    ArrayList<String> existingKeys = new ArrayList<>(existing.keySet());
+
+    int numMetrics = TestUtil.nextInt(random, 1, MAX_ITERATIONS);
+    for (int i = 0; i < numMetrics; ++i) {
+      boolean shouldReplaceMetric = !existing.isEmpty() && random.nextBoolean();
+      String name = shouldReplaceMetric
+          ? existingKeys.get(TestUtil.nextInt(random, 0, existingKeys.size() - 1))
+          : TestUtil.randomSimpleString(random, 1, 10) + SUFFIX; // must be simple string for JMX publishing
+
+      Counter counter = new Counter();
+      counter.inc(random.nextLong());
+      metrics.put(name, counter);
+    }
+
+    return metrics;
+  }
+
+  public static SolrMetricProducer getProducerOf(SolrMetricManager metricManager, SolrInfoMBean.Category category, String scope, Map<String, Counter> metrics) {
+    return new SolrMetricProducer() {
+      @Override
+      public Collection<String> initializeMetrics(SolrMetricManager manager, String registry, String scope) {
+        if (metrics == null || metrics.isEmpty()) {
+          return Collections.emptyList();
+        }
+        for (Map.Entry<String, Counter> entry : metrics.entrySet()) {
+          manager.counter(registry, entry.getKey(), category.toString(), scope);
+        }
+        return metrics.keySet();
+      }
+
+      @Override
+      public String getName() {
+        return scope;
+      }
+
+      @Override
+      public String getVersion() {
+        return "0.0";
+      }
+
+      @Override
+      public String getDescription() {
+        return "foo";
+      }
+
+      @Override
+      public Category getCategory() {
+        return category;
+      }
+
+      @Override
+      public String getSource() {
+        return null;
+      }
+
+      @Override
+      public URL[] getDocs() {
+        return new URL[0];
+      }
+
+      @Override
+      public NamedList getStatistics() {
+        return null;
+      }
+
+      @Override
+      public String toString() {
+        return "SolrMetricProducer.of{" +
+            "\ncategory=" + category +
+            "\nscope=" + scope +
+            "\nmetrics=" + metrics +
+            "\n}";
+      }
+    };
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8bbdb624/solr/core/src/test/org/apache/solr/metrics/SolrMetricsIntegrationTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/metrics/SolrMetricsIntegrationTest.java b/solr/core/src/test/org/apache/solr/metrics/SolrMetricsIntegrationTest.java
new file mode 100644
index 0000000..c6449ac
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/metrics/SolrMetricsIntegrationTest.java
@@ -0,0 +1,143 @@
+/*
+ * 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.solr.metrics;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Random;
+
+import com.codahale.metrics.Timer;
+import org.apache.commons.io.FileUtils;
+import org.apache.lucene.util.TestUtil;
+import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.core.CoreContainer;
+import org.apache.solr.core.NodeConfig;
+import org.apache.solr.core.PluginInfo;
+import org.apache.solr.core.SolrInfoMBean;
+import org.apache.solr.core.SolrResourceLoader;
+import org.apache.solr.core.SolrXmlConfig;
+import org.apache.solr.metrics.reporters.MockMetricReporter;
+import org.apache.solr.util.TestHarness;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class SolrMetricsIntegrationTest extends SolrTestCaseJ4 {
+  private static final int MAX_ITERATIONS = 20;
+  private static final String CORE_NAME = "metrics_integration";
+  private static final String METRIC_NAME = "requestTimes";
+  private static final String HANDLER_NAME = "standard";
+  private static final String[] REPORTER_NAMES = {"reporter1", "reporter2"};
+  private static final String UNIVERSAL = "universal";
+  private static final String SPECIFIC = "specific";
+  private static final String MULTIGROUP = "multigroup";
+  private static final String MULTIREGISTRY = "multiregistry";
+  private static final String[] INITIAL_REPORTERS = {REPORTER_NAMES[0], REPORTER_NAMES[1], UNIVERSAL, SPECIFIC, MULTIGROUP, MULTIREGISTRY};
+  private static final String[] RENAMED_REPORTERS = {REPORTER_NAMES[0], REPORTER_NAMES[1], UNIVERSAL, MULTIGROUP};
+  private static final SolrInfoMBean.Category HANDLER_CATEGORY = SolrInfoMBean.Category.QUERYHANDLER;
+
+  private CoreContainer cc;
+  private SolrMetricManager metricManager;
+
+  @Before
+  public void beforeTest() throws Exception {
+    Path home = Paths.get(TEST_HOME());
+    // define these properties, they are used in solrconfig.xml
+    System.setProperty("solr.test.sys.prop1", "propone");
+    System.setProperty("solr.test.sys.prop2", "proptwo");
+    String solrXml = FileUtils.readFileToString(Paths.get(home.toString(), "solr-metricreporter.xml").toFile(), "UTF-8");
+    NodeConfig cfg = SolrXmlConfig.fromString(new SolrResourceLoader(home), solrXml);
+    cc = createCoreContainer(cfg,
+        new TestHarness.TestCoresLocator(DEFAULT_TEST_CORENAME, initCoreDataDir.getAbsolutePath(), "solrconfig.xml", "schema.xml"));
+    h.coreName = DEFAULT_TEST_CORENAME;
+    metricManager = cc.getMetricManager();
+    // initially there are more reporters, because two of them are added via a matching collection name
+    Map<String, SolrMetricReporter> reporters = metricManager.getReporters("solr.core." + DEFAULT_TEST_CORENAME);
+    assertEquals(INITIAL_REPORTERS.length, reporters.size());
+    assertTrue(reporters.keySet().containsAll(Arrays.asList(INITIAL_REPORTERS)));
+    // test rename operation
+    cc.rename(DEFAULT_TEST_CORENAME, CORE_NAME);
+    h.coreName = CORE_NAME;
+    cfg = cc.getConfig();
+    PluginInfo[] plugins = cfg.getMetricReporterPlugins();
+    assertNotNull(plugins);
+    assertEquals(10, plugins.length);
+    reporters = metricManager.getReporters("solr.node");
+    assertEquals(4, reporters.size());
+    assertTrue("Reporter '" + REPORTER_NAMES[0] + "' missing in solr.node", reporters.containsKey(REPORTER_NAMES[0]));
+    assertTrue("Reporter '" + UNIVERSAL + "' missing in solr.node", reporters.containsKey(UNIVERSAL));
+    assertTrue("Reporter '" + MULTIGROUP + "' missing in solr.node", reporters.containsKey(MULTIGROUP));
+    assertTrue("Reporter '" + MULTIREGISTRY + "' missing in solr.node", reporters.containsKey(MULTIREGISTRY));
+    SolrMetricReporter reporter = reporters.get(REPORTER_NAMES[0]);
+    assertTrue("Reporter " + reporter + " is not an instance of " + MockMetricReporter.class.getName(),
+        reporter instanceof  MockMetricReporter);
+    reporter = reporters.get(UNIVERSAL);
+    assertTrue("Reporter " + reporter + " is not an instance of " + MockMetricReporter.class.getName(),
+        reporter instanceof  MockMetricReporter);
+  }
+
+  @After
+  public void afterTest() throws Exception {
+    SolrCoreMetricManager coreMetricManager = h.getCore().getCoreMetricManager();
+    Map<String, SolrMetricReporter> reporters = metricManager.getReporters(coreMetricManager.getRegistryName());
+
+    deleteCore();
+
+    for (String reporterName : RENAMED_REPORTERS) {
+      SolrMetricReporter reporter = reporters.get(reporterName);
+      MockMetricReporter mockReporter = (MockMetricReporter) reporter;
+      assertTrue("Reporter " + reporterName + " was not closed: " + mockReporter, mockReporter.didClose);
+    }
+  }
+
+  @Test
+  public void testConfigureReporter() throws Exception {
+    Random random = random();
+
+    String metricName = SolrMetricManager.mkName(METRIC_NAME, HANDLER_CATEGORY.toString(), HANDLER_NAME);
+    SolrCoreMetricManager coreMetricManager = h.getCore().getCoreMetricManager();
+    Timer timer = (Timer) metricManager.timer(coreMetricManager.getRegistryName(), metricName);
+
+    long initialCount = timer.getCount();
+
+    int iterations = TestUtil.nextInt(random, 0, MAX_ITERATIONS);
+    for (int i = 0; i < iterations; ++i) {
+      h.query(req("*"));
+    }
+
+    long finalCount = timer.getCount();
+    assertEquals("metric counter incorrect", iterations, finalCount - initialCount);
+    Map<String, SolrMetricReporter> reporters = metricManager.getReporters(coreMetricManager.getRegistryName());
+    assertEquals(RENAMED_REPORTERS.length, reporters.size());
+
+    // SPECIFIC and MULTIREGISTRY were skipped because they were
+    // specific to collection1
+    for (String reporterName : RENAMED_REPORTERS) {
+      SolrMetricReporter reporter = reporters.get(reporterName);
+      assertNotNull("Reporter " + reporterName + " was not found.", reporter);
+      assertTrue(reporter instanceof MockMetricReporter);
+
+      MockMetricReporter mockReporter = (MockMetricReporter) reporter;
+      assertTrue("Reporter " + reporterName + " was not initialized: " + mockReporter, mockReporter.didInit);
+      assertTrue("Reporter " + reporterName + " was not validated: " + mockReporter, mockReporter.didValidate);
+      assertFalse("Reporter " + reporterName + " was incorrectly closed: " + mockReporter, mockReporter.didClose);
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8bbdb624/solr/core/src/test/org/apache/solr/metrics/reporters/MockMetricReporter.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/metrics/reporters/MockMetricReporter.java b/solr/core/src/test/org/apache/solr/metrics/reporters/MockMetricReporter.java
new file mode 100644
index 0000000..2ecc33b
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/metrics/reporters/MockMetricReporter.java
@@ -0,0 +1,80 @@
+/*
+ * 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.solr.metrics.reporters;
+
+import java.io.IOException;
+import java.util.Locale;
+import java.util.NoSuchElementException;
+
+import com.codahale.metrics.Metric;
+import com.codahale.metrics.MetricRegistry;
+import org.apache.solr.core.PluginInfo;
+import org.apache.solr.metrics.SolrMetricManager;
+import org.apache.solr.metrics.SolrMetricReporter;
+
+public class MockMetricReporter extends SolrMetricReporter {
+
+  public String configurable;
+
+  public boolean didInit = false;
+  public boolean didClose = false;
+  public boolean didValidate = false;
+
+  public MockMetricReporter(SolrMetricManager metricManager, String registryName) {
+    super(metricManager, registryName);
+  }
+
+  @Override
+  public void init(PluginInfo pluginInfo) {
+    super.init(pluginInfo);
+    didInit = true;
+  }
+
+  @Override
+  public void close() throws IOException {
+    didClose = true;
+  }
+
+  @Override
+  protected void validate() throws IllegalStateException {
+    didValidate = true;
+    if (configurable == null) {
+      throw new IllegalStateException("MockMetricReporter::configurable not defined.");
+    }
+  }
+
+  public void setConfigurable(String configurable) {
+    this.configurable = configurable;
+  }
+
+  public Metric reportMetric(String metricName) throws NoSuchElementException {
+    MetricRegistry registry = metricManager.registry(registryName);
+    Metric metric = registry.getMetrics().get(metricName);
+    if (metric == null) {
+      throw new NoSuchElementException("Metric was not found for metric name = " + metricName);
+    }
+
+    return metric;
+  }
+
+  @Override
+  public String toString() {
+    return String.format(Locale.ENGLISH, "[%s@%s: configurable = %s, didInit = %b, didValidate = %b, didClose = %b]",
+        getClass().getName(), Integer.toHexString(hashCode()), configurable, didInit, didValidate, didClose);
+
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8bbdb624/solr/core/src/test/org/apache/solr/metrics/reporters/SolrJmxReporterTest.java
----------------------------------------------------------------------
diff --git a/solr/core/src/test/org/apache/solr/metrics/reporters/SolrJmxReporterTest.java b/solr/core/src/test/org/apache/solr/metrics/reporters/SolrJmxReporterTest.java
new file mode 100644
index 0000000..ea452b2
--- /dev/null
+++ b/solr/core/src/test/org/apache/solr/metrics/reporters/SolrJmxReporterTest.java
@@ -0,0 +1,156 @@
+/*
+ * 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.solr.metrics.reporters;
+
+import javax.management.MBeanServer;
+import javax.management.ObjectInstance;
+import javax.management.ObjectName;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Random;
+import java.util.Set;
+
+import com.codahale.metrics.Counter;
+import org.apache.lucene.util.TestUtil;
+import org.apache.solr.SolrTestCaseJ4;
+import org.apache.solr.common.params.CoreAdminParams;
+import org.apache.solr.core.PluginInfo;
+import org.apache.solr.core.SolrCore;
+import org.apache.solr.core.SolrInfoMBean;
+import org.apache.solr.metrics.SolrCoreMetricManager;
+import org.apache.solr.metrics.SolrMetricManager;
+import org.apache.solr.metrics.SolrMetricProducer;
+import org.apache.solr.metrics.SolrMetricReporter;
+import org.apache.solr.metrics.SolrMetricTestUtils;
+import org.apache.solr.schema.FieldType;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class SolrJmxReporterTest extends SolrTestCaseJ4 {
+
+  private static final int MAX_ITERATIONS = 20;
+
+  private String domain;
+
+  private SolrCoreMetricManager coreMetricManager;
+  private SolrMetricManager metricManager;
+  private SolrJmxReporter reporter;
+  private MBeanServer mBeanServer;
+  private String reporterName;
+
+  @Before
+  public void beforeTest() throws Exception {
+    initCore("solrconfig-basic.xml", "schema.xml");
+
+    final SolrCore core = h.getCore();
+    domain = core.getName();
+
+    coreMetricManager = core.getCoreMetricManager();
+    metricManager = core.getCoreDescriptor().getCoreContainer().getMetricManager();
+    PluginInfo pluginInfo = createReporterPluginInfo();
+    metricManager.loadReporter(coreMetricManager.getRegistryName(), coreMetricManager.getCore().getResourceLoader(), pluginInfo);
+
+    Map<String, SolrMetricReporter> reporters = metricManager.getReporters(coreMetricManager.getRegistryName());
+    assertTrue("reporters.size should be > 0, but was + " + reporters.size(), reporters.size() > 0);
+    reporterName = pluginInfo.name;
+    assertNotNull("reporter " + reporterName + " not present among " + reporters, reporters.get(reporterName));
+    assertTrue("wrong reporter class: " + reporters.get(reporterName), reporters.get(reporterName) instanceof SolrJmxReporter);
+
+    reporter = (SolrJmxReporter) reporters.get(reporterName);
+    mBeanServer = reporter.getMBeanServer();
+    assertNotNull("MBean server not found.", mBeanServer);
+  }
+
+  private PluginInfo createReporterPluginInfo() {
+    Random random = random();
+    String className = SolrJmxReporter.class.getName();
+    String reporterName = TestUtil.randomSimpleString(random, 1, 10);
+
+    Map<String, Object> attrs = new HashMap<>();
+    attrs.put(FieldType.CLASS_NAME, className);
+    attrs.put(CoreAdminParams.NAME, reporterName);
+
+    boolean shouldOverrideDomain = random.nextBoolean();
+    if (shouldOverrideDomain) {
+      domain = TestUtil.randomSimpleString(random);
+      attrs.put("domain", domain);
+    }
+
+    return new PluginInfo(TestUtil.randomUnicodeString(random), attrs);
+  }
+
+  @After
+  public void afterTest() throws Exception {
+    metricManager.closeReporters(coreMetricManager.getRegistryName());
+    Set<ObjectInstance> objects =
+        mBeanServer.queryMBeans(ObjectName.getInstance(domain + ":*"), null);
+    assertTrue(objects.isEmpty());
+
+    coreMetricManager.close();
+    deleteCore();
+  }
+
+  @Test
+  public void testReportMetrics() throws Exception {
+    Random random = random();
+
+    Map<String, Counter> registered = new HashMap<>();
+    String scope = SolrMetricTestUtils.getRandomScope(random, true);
+    SolrInfoMBean.Category category = SolrMetricTestUtils.getRandomCategory(random, true);
+
+    int iterations = TestUtil.nextInt(random, 0, MAX_ITERATIONS);
+    for (int i = 0; i < iterations; ++i) {
+      Map<String, Counter> metrics = SolrMetricTestUtils.getRandomMetricsWithReplacements(random, registered);
+      SolrMetricProducer producer = SolrMetricTestUtils.getProducerOf(metricManager, category, scope, metrics);
+      coreMetricManager.registerMetricProducer(scope, producer);
+      registered.putAll(metrics);
+      //waitForListener();
+      Set<ObjectInstance> objects = mBeanServer.queryMBeans(null, null);
+      assertEquals(registered.size(), objects.stream().
+          filter(o -> scope.equals(o.getObjectName().getKeyProperty("scope")) &&
+                      reporterName.equals(o.getObjectName().getKeyProperty("reporter"))).count());
+    }
+  }
+
+  @Test
+  public void testReloadCore() throws Exception {
+    Random random = random();
+
+    String scope = SolrMetricTestUtils.getRandomScope(random, true);
+    SolrInfoMBean.Category category = SolrMetricTestUtils.getRandomCategory(random, true);
+    Map<String, Counter> metrics = SolrMetricTestUtils.getRandomMetrics(random, true);
+    SolrMetricProducer producer = SolrMetricTestUtils.getProducerOf(metricManager, category, scope, metrics);
+    coreMetricManager.registerMetricProducer(scope, producer);
+    Set<ObjectInstance> objects = mBeanServer.queryMBeans(null, null);
+    assertEquals(metrics.size(), objects.stream().
+        filter(o -> scope.equals(o.getObjectName().getKeyProperty("scope")) &&
+            reporterName.equals(o.getObjectName().getKeyProperty("reporter"))).count());
+
+    h.getCoreContainer().reload(h.getCore().getName());
+    PluginInfo pluginInfo = createReporterPluginInfo();
+    metricManager.loadReporter(coreMetricManager.getRegistryName(), coreMetricManager.getCore().getResourceLoader(), pluginInfo);
+    coreMetricManager.registerMetricProducer(scope, producer);
+
+    objects = mBeanServer.queryMBeans(null, null);
+    assertEquals(metrics.size(), objects.stream().
+        filter(o -> scope.equals(o.getObjectName().getKeyProperty("scope")) &&
+            pluginInfo.name.equals(o.getObjectName().getKeyProperty("reporter"))).count());
+  }
+
+}


[3/3] lucene-solr:master: Squashed commit of branch 'feature/metrics', containing: SOLR-4735: Improve Solr metrics reporting SOLR-9812: Implement /admin/metrics API SOLR-9805: Use metrics-jvm library to instrument jvm internals SOLR-9788:

Posted by ab...@apache.org.
Squashed commit of branch 'feature/metrics', containing:
    SOLR-4735: Improve Solr metrics reporting
    SOLR-9812: Implement /admin/metrics API
    SOLR-9805: Use metrics-jvm library to instrument jvm internals
    SOLR-9788: Use instrumented jetty classes


Project: http://git-wip-us.apache.org/repos/asf/lucene-solr/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucene-solr/commit/8bbdb624
Tree: http://git-wip-us.apache.org/repos/asf/lucene-solr/tree/8bbdb624
Diff: http://git-wip-us.apache.org/repos/asf/lucene-solr/diff/8bbdb624

Branch: refs/heads/master
Commit: 8bbdb6248c5de3f3bd61501ba42a50aeec29c78b
Parents: 84bbb8f
Author: Andrzej Bialecki <ab...@apache.org>
Authored: Tue Dec 20 09:31:24 2016 +0100
Committer: Andrzej Bialecki <ab...@apache.org>
Committed: Tue Dec 20 09:31:24 2016 +0100

----------------------------------------------------------------------
 .../idea/solr/contrib/analytics/analytics.iml   |   1 +
 lucene/ivy-versions.properties                  |   2 +
 solr/CHANGES.txt                                |  13 +
 .../plugin/AnalyticsStatisticsCollector.java    |   4 +-
 solr/core/ivy.xml                               |   1 -
 .../apache/solr/cloud/OverseerStatusCmd.java    |   4 +-
 .../org/apache/solr/core/CoreContainer.java     |  66 +-
 .../java/org/apache/solr/core/NodeConfig.java   |  18 +-
 .../java/org/apache/solr/core/PluginInfo.java   |   4 +-
 .../src/java/org/apache/solr/core/SolrCore.java |  73 +++
 .../org/apache/solr/core/SolrInfoMBean.java     |  10 +-
 .../org/apache/solr/core/SolrXmlConfig.java     |  14 +-
 .../apache/solr/handler/RequestHandlerBase.java |  58 +-
 .../solr/handler/admin/MetricsHandler.java      | 164 +++++
 .../solr/metrics/SolrCoreMetricManager.java     | 154 +++++
 .../org/apache/solr/metrics/SolrMetricInfo.java | 104 +++
 .../apache/solr/metrics/SolrMetricManager.java  | 652 +++++++++++++++++++
 .../apache/solr/metrics/SolrMetricProducer.java |  38 ++
 .../apache/solr/metrics/SolrMetricReporter.java |  83 +++
 .../org/apache/solr/metrics/package-info.java   |  23 +
 .../solr/metrics/reporters/SolrJmxReporter.java | 284 ++++++++
 .../solr/metrics/reporters/package-info.java    |  22 +
 .../solr/security/PermissionNameProvider.java   |   1 +
 .../apache/solr/servlet/SolrDispatchFilter.java |  27 +
 .../src/java/org/apache/solr/util/JmxUtil.java  |  78 +++
 .../org/apache/solr/util/stats/MetricUtils.java | 144 ++++
 .../org/apache/solr/util/stats/TimerUtils.java  |  58 --
 .../src/test-files/solr/solr-metricreporter.xml |  57 ++
 .../solr/handler/admin/MetricsHandlerTest.java  |  97 +++
 .../solr/metrics/SolrCoreMetricManagerTest.java | 172 +++++
 .../solr/metrics/SolrMetricManagerTest.java     | 273 ++++++++
 .../solr/metrics/SolrMetricReporterTest.java    |  69 ++
 .../solr/metrics/SolrMetricTestUtils.java       | 140 ++++
 .../metrics/SolrMetricsIntegrationTest.java     | 143 ++++
 .../metrics/reporters/MockMetricReporter.java   |  80 +++
 .../metrics/reporters/SolrJmxReporterTest.java  | 156 +++++
 .../apache/solr/util/stats/MetricUtilsTest.java |  58 ++
 .../apache/solr/util/stats/TimerUtilsTest.java  |  58 --
 solr/licenses/metrics-jetty-LICENSE-ASL.txt     | 203 ++++++
 solr/licenses/metrics-jetty-NOTICE.txt          |  12 +
 solr/licenses/metrics-jetty9-3.1.2.jar.sha1     |   1 +
 solr/licenses/metrics-json-LICENSE-ASL.txt      | 203 ++++++
 solr/licenses/metrics-json-NOTICE.txt           |  12 +
 solr/licenses/metrics-jvm-3.1.2.jar.sha1        |   1 +
 solr/licenses/metrics-jvm-LICENSE-ASL.txt       | 203 ++++++
 solr/licenses/metrics-jvm-NOTICE.txt            |  12 +
 solr/licenses/metrics-servlets-LICENSE-ASL.txt  | 203 ++++++
 solr/licenses/metrics-servlets-NOTICE.txt       |  12 +
 solr/server/build.xml                           |   4 +-
 solr/server/etc/jetty.xml                       |  20 +-
 solr/server/ivy.xml                             |  11 +-
 .../apache/solr/common/params/CommonParams.java |   4 +-
 .../java/org/apache/solr/util/TestHarness.java  |   9 +
 53 files changed, 4145 insertions(+), 168 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8bbdb624/dev-tools/idea/solr/contrib/analytics/analytics.iml
----------------------------------------------------------------------
diff --git a/dev-tools/idea/solr/contrib/analytics/analytics.iml b/dev-tools/idea/solr/contrib/analytics/analytics.iml
index 10f51a7..d63d3e2 100644
--- a/dev-tools/idea/solr/contrib/analytics/analytics.iml
+++ b/dev-tools/idea/solr/contrib/analytics/analytics.iml
@@ -14,6 +14,7 @@
     <orderEntry type="library" scope="TEST" name="JUnit" level="project" />
     <orderEntry type="library" name="Solr core library" level="project" />
     <orderEntry type="library" name="Solrj library" level="project" />
+    <orderEntry type="library" name="Solr example library" level="project" />
     <orderEntry type="module" scope="TEST" module-name="lucene-test-framework" />
     <orderEntry type="module" scope="TEST" module-name="solr-test-framework" />
     <orderEntry type="module" module-name="lucene-core" />

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8bbdb624/lucene/ivy-versions.properties
----------------------------------------------------------------------
diff --git a/lucene/ivy-versions.properties b/lucene/ivy-versions.properties
index ffc54a8..2f44f7e 100644
--- a/lucene/ivy-versions.properties
+++ b/lucene/ivy-versions.properties
@@ -76,6 +76,8 @@ com.sun.jersey.version = 1.9
 io.dropwizard.metrics.version = 3.1.2
 /io.dropwizard.metrics/metrics-core = ${io.dropwizard.metrics.version}
 /io.dropwizard.metrics/metrics-healthchecks = ${io.dropwizard.metrics.version}
+/io.dropwizard.metrics/metrics-jetty9 = ${io.dropwizard.metrics.version}
+/io.dropwizard.metrics/metrics-jvm = ${io.dropwizard.metrics.version}
 
 io.netty.netty-all.version = 4.0.36.Final
 /io.netty/netty-all = ${io.netty.netty-all.version}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8bbdb624/solr/CHANGES.txt
----------------------------------------------------------------------
diff --git a/solr/CHANGES.txt b/solr/CHANGES.txt
index d04d491..519cdfa 100644
--- a/solr/CHANGES.txt
+++ b/solr/CHANGES.txt
@@ -173,11 +173,24 @@ New Features
 * SOLR-9844: FieldCache information fetched via the mbeans handler or seen via the UI now displays the total size used.
   The individual cache entries in the response are now formatted better as well. (Varun Thacker)
 
+<<<<<<< HEAD
 * SOLR-9513: Generic authentication plugins (GenericHadoopAuthPlugin and ConfigurableInternodeAuthHadoopPlugin) that delegate
   all functionality to Hadoop authentication framework. (Hrishikesh Gadre via Ishan Chattopadhyaya)
 
 * SOLR-9860: Enable configuring invariantParams via HttpSolrClient.Builder (Hrishikesh Gadre, Ishan Chattopadhyaya)
  
+* SOLR-4735: Improve metrics reporting. This uses the dropwizard metric library, adding an internal API
+  for registering and reporting metrics from Solr components. Several new metrics and an improved JMX
+  reporter have been added (Alan Woodward, Jeff Wartes, Christine Poerschke, Kelvin Wong, shalin, ab)
+
+* SOLR-9788: Use instrumented jetty classes provided by the dropwizard metric library. (shalin)
+
+* SOLR-9805: Use metrics-jvm library to instrument jvm internals such as GC, memory usage and others. (shalin)
+
+* SOLR-9812: Added a new /admin/metrics API to return all metrics collected by Solr via API. API supports two
+  optional parameters 'group' (all,jvm,jetty,http,node,core) and 'type' (all,counter,timer,gauge,histogram) both
+  of which are multi-valued. Example: http://localhost:8983/solr/admin/metrics?group=jvm,jetty&type=counter
+  (shalin)
 
 Optimizations
 ----------------------

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8bbdb624/solr/contrib/analytics/src/java/org/apache/solr/analytics/plugin/AnalyticsStatisticsCollector.java
----------------------------------------------------------------------
diff --git a/solr/contrib/analytics/src/java/org/apache/solr/analytics/plugin/AnalyticsStatisticsCollector.java b/solr/contrib/analytics/src/java/org/apache/solr/analytics/plugin/AnalyticsStatisticsCollector.java
index e64c950..b22dcb5 100644
--- a/solr/contrib/analytics/src/java/org/apache/solr/analytics/plugin/AnalyticsStatisticsCollector.java
+++ b/solr/contrib/analytics/src/java/org/apache/solr/analytics/plugin/AnalyticsStatisticsCollector.java
@@ -21,7 +21,7 @@ import java.util.concurrent.atomic.AtomicLong;
 import com.codahale.metrics.Timer;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.common.util.SimpleOrderedMap;
-import org.apache.solr.util.stats.TimerUtils;
+import org.apache.solr.util.stats.MetricUtils;
 
 public class AnalyticsStatisticsCollector {
   private final AtomicLong numRequests;
@@ -95,7 +95,7 @@ public class AnalyticsStatisticsCollector {
     lst.add("rangeFacets", numRangeFacets.longValue());
     lst.add("queryFacets", numQueryFacets.longValue());
     lst.add("queriesInQueryFacets", numQueries.longValue());
-    TimerUtils.addMetrics(lst, requestTimes);
+    MetricUtils.addMetrics(lst, requestTimes);
     return lst;
   }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8bbdb624/solr/core/ivy.xml
----------------------------------------------------------------------
diff --git a/solr/core/ivy.xml b/solr/core/ivy.xml
index 5710da9..67e4379 100644
--- a/solr/core/ivy.xml
+++ b/solr/core/ivy.xml
@@ -50,7 +50,6 @@
     <dependency org="log4j" name="log4j" rev="${/log4j/log4j}" conf="compile"/>
     <dependency org="org.slf4j" name="slf4j-log4j12" rev="${/org.slf4j/slf4j-log4j12}" conf="compile"/>
     <dependency org="org.slf4j" name="jcl-over-slf4j" rev="${/org.slf4j/jcl-over-slf4j}" conf="compile"/>
-    <dependency org="io.dropwizard.metrics" name="metrics-core" rev="${/io.dropwizard.metrics/metrics-core}" conf="compile" />
 
     <dependency org="org.easymock" name="easymock" rev="${/org.easymock/easymock}" conf="test"/>
     <dependency org="cglib" name="cglib-nodep" rev="${/cglib/cglib-nodep}" conf="test"/>

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8bbdb624/solr/core/src/java/org/apache/solr/cloud/OverseerStatusCmd.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/cloud/OverseerStatusCmd.java b/solr/core/src/java/org/apache/solr/cloud/OverseerStatusCmd.java
index 901a884..a24deb1 100644
--- a/solr/core/src/java/org/apache/solr/cloud/OverseerStatusCmd.java
+++ b/solr/core/src/java/org/apache/solr/cloud/OverseerStatusCmd.java
@@ -30,7 +30,7 @@ import org.apache.solr.common.cloud.ZkNodeProps;
 import org.apache.solr.common.cloud.ZkStateReader;
 import org.apache.solr.common.util.NamedList;
 import org.apache.solr.common.util.SimpleOrderedMap;
-import org.apache.solr.util.stats.TimerUtils;
+import org.apache.solr.util.stats.MetricUtils;
 import org.apache.zookeeper.data.Stat;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -100,7 +100,7 @@ public class OverseerStatusCmd implements Cmd {
         lst.add("errors", errors);
       }
       Timer timer = entry.getValue().requestTime;
-      TimerUtils.addMetrics(lst, timer);
+      MetricUtils.addMetrics(lst, timer);
     }
     results.add("overseer_operations", overseerStats);
     results.add("collection_operations", collectionStats);

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8bbdb624/solr/core/src/java/org/apache/solr/core/CoreContainer.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/core/CoreContainer.java b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
index 7c38b81..6e640bc 100644
--- a/solr/core/src/java/org/apache/solr/core/CoreContainer.java
+++ b/solr/core/src/java/org/apache/solr/core/CoreContainer.java
@@ -33,6 +33,7 @@ import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Future;
 
+import com.codahale.metrics.Gauge;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Maps;
 import org.apache.http.auth.AuthSchemeProvider;
@@ -58,6 +59,7 @@ import org.apache.solr.handler.admin.CollectionsHandler;
 import org.apache.solr.handler.admin.ConfigSetsHandler;
 import org.apache.solr.handler.admin.CoreAdminHandler;
 import org.apache.solr.handler.admin.InfoHandler;
+import org.apache.solr.handler.admin.MetricsHandler;
 import org.apache.solr.handler.admin.SecurityConfHandler;
 import org.apache.solr.handler.admin.SecurityConfHandlerLocal;
 import org.apache.solr.handler.admin.SecurityConfHandlerZk;
@@ -65,6 +67,8 @@ import org.apache.solr.handler.admin.ZookeeperInfoHandler;
 import org.apache.solr.handler.component.ShardHandlerFactory;
 import org.apache.solr.logging.LogWatcher;
 import org.apache.solr.logging.MDCLoggingContext;
+import org.apache.solr.metrics.SolrMetricManager;
+import org.apache.solr.metrics.SolrMetricProducer;
 import org.apache.solr.request.SolrRequestHandler;
 import org.apache.solr.security.AuthenticationPlugin;
 import org.apache.solr.security.AuthorizationPlugin;
@@ -85,6 +89,7 @@ import static org.apache.solr.common.params.CommonParams.COLLECTIONS_HANDLER_PAT
 import static org.apache.solr.common.params.CommonParams.CONFIGSETS_HANDLER_PATH;
 import static org.apache.solr.common.params.CommonParams.CORES_HANDLER_PATH;
 import static org.apache.solr.common.params.CommonParams.INFO_HANDLER_PATH;
+import static org.apache.solr.common.params.CommonParams.METRICS_PATH;
 import static org.apache.solr.common.params.CommonParams.ZK_PATH;
 import static org.apache.solr.security.AuthenticationPlugin.AUTHENTICATION_PLUGIN_PROP;
 
@@ -156,6 +161,10 @@ public class CoreContainer {
 
   private BackupRepositoryFactory backupRepoFactory;
 
+  protected SolrMetricManager metricManager;
+
+  protected MetricsHandler metricsHandler;
+
   /**
    * This method instantiates a new instance of {@linkplain BackupRepository}.
    *
@@ -423,6 +432,10 @@ public class CoreContainer {
     return pkiAuthenticationPlugin;
   }
 
+  public SolrMetricManager getMetricManager() {
+    return metricManager;
+  }
+
   //-------------------------------------------------------------------
   // Initialization / Cleanup
   //-------------------------------------------------------------------
@@ -463,28 +476,45 @@ public class CoreContainer {
 
     MDCLoggingContext.setNode(this);
 
+    metricManager = new SolrMetricManager();
+
     securityConfHandler = isZooKeeperAware() ? new SecurityConfHandlerZk(this) : new SecurityConfHandlerLocal(this);
     reloadSecurityProperties();
     this.backupRepoFactory = new BackupRepositoryFactory(cfg.getBackupRepositoryPlugins());
 
-    containerHandlers.put(ZK_PATH, new ZookeeperInfoHandler(this));
-    collectionsHandler = createHandler(cfg.getCollectionsHandlerClass(), CollectionsHandler.class);
-    containerHandlers.put(COLLECTIONS_HANDLER_PATH, collectionsHandler);
-    infoHandler        = createHandler(cfg.getInfoHandlerClass(), InfoHandler.class);
-    containerHandlers.put(INFO_HANDLER_PATH, infoHandler);
-    coreAdminHandler   = createHandler(cfg.getCoreAdminHandlerClass(), CoreAdminHandler.class);
-    containerHandlers.put(CORES_HANDLER_PATH, coreAdminHandler);
-    configSetsHandler = createHandler(cfg.getConfigSetsHandlerClass(), ConfigSetsHandler.class);
-    containerHandlers.put(CONFIGSETS_HANDLER_PATH, configSetsHandler);
+    createHandler(ZK_PATH, ZookeeperInfoHandler.class.getName(), ZookeeperInfoHandler.class);
+    collectionsHandler = createHandler(COLLECTIONS_HANDLER_PATH, cfg.getCollectionsHandlerClass(), CollectionsHandler.class);
+    infoHandler        = createHandler(INFO_HANDLER_PATH, cfg.getInfoHandlerClass(), InfoHandler.class);
+    coreAdminHandler   = createHandler(CORES_HANDLER_PATH, cfg.getCoreAdminHandlerClass(), CoreAdminHandler.class);
+    configSetsHandler = createHandler(CONFIGSETS_HANDLER_PATH, cfg.getConfigSetsHandlerClass(), ConfigSetsHandler.class);
+    metricsHandler = createHandler(METRICS_PATH, MetricsHandler.class.getName(), MetricsHandler.class);
     containerHandlers.put(AUTHZ_PATH, securityConfHandler);
+    securityConfHandler.initializeMetrics(metricManager, SolrInfoMBean.Group.node.toString(), AUTHZ_PATH);
     containerHandlers.put(AUTHC_PATH, securityConfHandler);
     if(pkiAuthenticationPlugin != null)
       containerHandlers.put(PKIAuthenticationPlugin.PATH, pkiAuthenticationPlugin.getRequestHandler());
 
+    metricManager.loadReporters(cfg.getMetricReporterPlugins(), loader, SolrInfoMBean.Group.node);
+    metricManager.loadReporters(cfg.getMetricReporterPlugins(), loader, SolrInfoMBean.Group.jvm);
+    metricManager.loadReporters(cfg.getMetricReporterPlugins(), loader, SolrInfoMBean.Group.jetty);
+    metricManager.loadReporters(cfg.getMetricReporterPlugins(), loader, SolrInfoMBean.Group.http);
+
     coreConfigService = ConfigSetService.createConfigSetService(cfg, loader, zkSys.zkController);
 
     containerProperties.putAll(cfg.getSolrProperties());
 
+    // initialize gauges for reporting the number of cores
+    Gauge<Integer> loadedCores = () -> solrCores.getCores().size();
+    Gauge<Integer> lazyCores = () -> solrCores.getCoreNames().size() - solrCores.getCores().size();
+    Gauge<Integer> unloadedCores = () -> solrCores.getAllCoreNames().size() - solrCores.getCoreNames().size();
+
+    metricManager.register(SolrMetricManager.getRegistryName(SolrInfoMBean.Group.node),
+        loadedCores, true, "loaded", "cores");
+    metricManager.register(SolrMetricManager.getRegistryName(SolrInfoMBean.Group.node),
+        lazyCores, true, "lazy", "cores");
+    metricManager.register(SolrMetricManager.getRegistryName(SolrInfoMBean.Group.node),
+        unloadedCores, true, "unloaded", "cores");
+
     // setup executor to load cores in parallel
     ExecutorService coreLoadExecutor = ExecutorUtil.newMDCAwareFixedThreadPool(
         cfg.getCoreLoadThreadCount(isZooKeeperAware()),
@@ -658,6 +688,10 @@ public class CoreContainer {
       }
     }
 
+    if (metricManager != null) {
+      metricManager.closeReporters(SolrMetricManager.getRegistryName(SolrInfoMBean.Group.node));
+    }
+
     // It should be safe to close the authorization plugin at this point.
     try {
       if(authorizationPlugin != null) {
@@ -1034,6 +1068,9 @@ public class CoreContainer {
     SolrCore core = solrCores.remove(name);
     coresLocator.delete(this, cd);
 
+    // delete metrics specific to this core
+    metricManager.removeRegistry(SolrMetricManager.getRegistryName(SolrInfoMBean.Group.core, name));
+
     if (core == null) {
       // transient core
       SolrCore.deleteUnloadedCore(cd, deleteDataDir, deleteInstanceDir);
@@ -1167,8 +1204,15 @@ public class CoreContainer {
 
   // ---------------- CoreContainer request handlers --------------
 
-  protected <T> T createHandler(String handlerClass, Class<T> clazz) {
-    return loader.newInstance(handlerClass, clazz, null, new Class[] { CoreContainer.class }, new Object[] { this });
+  protected <T> T createHandler(String path, String handlerClass, Class<T> clazz) {
+    T handler = loader.newInstance(handlerClass, clazz, null, new Class[] { CoreContainer.class }, new Object[] { this });
+    if (handler instanceof SolrRequestHandler) {
+      containerHandlers.put(path, (SolrRequestHandler)handler);
+    }
+    if (handler instanceof SolrMetricProducer) {
+      ((SolrMetricProducer)handler).initializeMetrics(metricManager, SolrInfoMBean.Group.node.toString(), path);
+    }
+    return handler;
   }
 
   public CoreAdminHandler getMultiCoreHandler() {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8bbdb624/solr/core/src/java/org/apache/solr/core/NodeConfig.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/core/NodeConfig.java b/solr/core/src/java/org/apache/solr/core/NodeConfig.java
index ea451a9..258fd14 100644
--- a/solr/core/src/java/org/apache/solr/core/NodeConfig.java
+++ b/solr/core/src/java/org/apache/solr/core/NodeConfig.java
@@ -60,13 +60,16 @@ public class NodeConfig {
 
   private final PluginInfo[] backupRepositoryPlugins;
 
+  private final PluginInfo[] metricReporterPlugins;
+
   private NodeConfig(String nodeName, Path coreRootDirectory, Path configSetBaseDirectory, String sharedLibDirectory,
                      PluginInfo shardHandlerFactoryConfig, UpdateShardHandlerConfig updateShardHandlerConfig,
                      String coreAdminHandlerClass, String collectionsAdminHandlerClass,
                      String infoHandlerClass, String configSetsHandlerClass,
                      LogWatcherConfig logWatcherConfig, CloudConfig cloudConfig, Integer coreLoadThreads,
                      int transientCacheSize, boolean useSchemaCache, String managementPath, SolrResourceLoader loader,
-                     Properties solrProperties, PluginInfo[] backupRepositoryPlugins) {
+                     Properties solrProperties, PluginInfo[] backupRepositoryPlugins,
+                     PluginInfo[] metricReporterPlugins) {
     this.nodeName = nodeName;
     this.coreRootDirectory = coreRootDirectory;
     this.configSetBaseDirectory = configSetBaseDirectory;
@@ -86,6 +89,7 @@ public class NodeConfig {
     this.loader = loader;
     this.solrProperties = solrProperties;
     this.backupRepositoryPlugins = backupRepositoryPlugins;
+    this.metricReporterPlugins = metricReporterPlugins;
 
     if (this.cloudConfig != null && this.getCoreLoadThreadCount(false) < 2) {
       throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
@@ -174,6 +178,10 @@ public class NodeConfig {
     return backupRepositoryPlugins;
   }
 
+  public PluginInfo[] getMetricReporterPlugins() {
+    return metricReporterPlugins;
+  }
+
   public static class NodeConfigBuilder {
 
     private Path coreRootDirectory;
@@ -193,6 +201,7 @@ public class NodeConfig {
     private String managementPath;
     private Properties solrProperties = new Properties();
     private PluginInfo[] backupRepositoryPlugins;
+    private PluginInfo[] metricReporterPlugins;
 
     private final SolrResourceLoader loader;
     private final String nodeName;
@@ -300,11 +309,16 @@ public class NodeConfig {
       return this;
     }
 
+    public NodeConfigBuilder setMetricReporterPlugins(PluginInfo[] metricReporterPlugins) {
+      this.metricReporterPlugins = metricReporterPlugins;
+      return this;
+    }
+
     public NodeConfig build() {
       return new NodeConfig(nodeName, coreRootDirectory, configSetBaseDirectory, sharedLibDirectory, shardHandlerFactoryConfig,
                             updateShardHandlerConfig, coreAdminHandlerClass, collectionsAdminHandlerClass, infoHandlerClass, configSetsHandlerClass,
                             logWatcherConfig, cloudConfig, coreLoadThreads, transientCacheSize, useSchemaCache, managementPath, loader, solrProperties,
-                            backupRepositoryPlugins);
+                            backupRepositoryPlugins, metricReporterPlugins);
     }
   }
 }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8bbdb624/solr/core/src/java/org/apache/solr/core/PluginInfo.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/core/PluginInfo.java b/solr/core/src/java/org/apache/solr/core/PluginInfo.java
index 5fdef3c..10f8b8d 100644
--- a/solr/core/src/java/org/apache/solr/core/PluginInfo.java
+++ b/solr/core/src/java/org/apache/solr/core/PluginInfo.java
@@ -111,6 +111,7 @@ public class PluginInfo implements MapSerializable {
     if (type != null) sb.append("type = " + type + ",");
     if (name != null) sb.append("name = " + name + ",");
     if (className != null) sb.append("class = " + className + ",");
+    if (attributes != null && attributes.size() > 0) sb.append("attributes = " + attributes + ",");
     if (initArgs != null && initArgs.size() > 0) sb.append("args = " + initArgs);
     sb.append("}");
     return sb.toString();
@@ -181,7 +182,8 @@ public class PluginInfo implements MapSerializable {
 
   }
   public PluginInfo copy() {
-    PluginInfo result = new PluginInfo(type, attributes, initArgs.clone(), children);
+    PluginInfo result = new PluginInfo(type, attributes,
+        initArgs != null ? initArgs.clone() : null, children);
     result.isFromSolrConfig = isFromSolrConfig;
     return result;
   }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8bbdb624/solr/core/src/java/org/apache/solr/core/SolrCore.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/core/SolrCore.java b/solr/core/src/java/org/apache/solr/core/SolrCore.java
index f75e780..a8d7738 100644
--- a/solr/core/src/java/org/apache/solr/core/SolrCore.java
+++ b/solr/core/src/java/org/apache/solr/core/SolrCore.java
@@ -53,6 +53,8 @@ import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.locks.ReentrantLock;
 
+import com.codahale.metrics.Counter;
+import com.codahale.metrics.Timer;
 import com.google.common.collect.MapMaker;
 import org.apache.commons.io.FileUtils;
 import org.apache.lucene.analysis.util.ResourceLoader;
@@ -94,6 +96,9 @@ import org.apache.solr.handler.RequestHandlerBase;
 import org.apache.solr.handler.component.HighlightComponent;
 import org.apache.solr.handler.component.SearchComponent;
 import org.apache.solr.logging.MDCLoggingContext;
+import org.apache.solr.metrics.SolrCoreMetricManager;
+import org.apache.solr.metrics.SolrMetricManager;
+import org.apache.solr.metrics.SolrMetricProducer;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.request.SolrRequestHandler;
 import org.apache.solr.response.BinaryResponseWriter;
@@ -187,6 +192,7 @@ public final class SolrCore implements SolrInfoMBean, Closeable {
   private final PluginBag<SearchComponent> searchComponents = new PluginBag<>(SearchComponent.class, this);
   private final PluginBag<UpdateRequestProcessorFactory> updateProcessors = new PluginBag<>(UpdateRequestProcessorFactory.class, this, true);
   private final Map<String,UpdateRequestProcessorChain> updateProcessorChains;
+  private final SolrCoreMetricManager coreMetricManager;
   private final Map<String, SolrInfoMBean> infoRegistry;
   private final IndexDeletionPolicyWrapper solrDelPolicy;
   private final SolrSnapshotMetaDataManager snapshotMgr;
@@ -200,6 +206,12 @@ public final class SolrCore implements SolrInfoMBean, Closeable {
   private final ReentrantLock ruleExpiryLock;
   private final ReentrantLock snapshotDelLock; // A lock instance to guard against concurrent deletions.
 
+  private final Timer newSearcherTimer;
+  private final Timer newSearcherWarmupTimer;
+  private final Counter newSearcherCounter;
+  private final Counter newSearcherMaxReachedCounter;
+  private final Counter newSearcherOtherErrorsCounter;
+
   public Date getStartTimeStamp() { return startTime; }
 
   private final Map<Object, IndexFingerprint> perSegmentFingerprintCache = new MapMaker().weakKeys().makeMap();
@@ -386,9 +398,13 @@ public final class SolrCore implements SolrInfoMBean, Closeable {
   }
 
   public void setName(String v) {
+    String oldName = this.name;
     this.name = v;
     this.logid = (v==null)?"":("["+v+"] ");
     this.coreDescriptor = new CoreDescriptor(v, this.coreDescriptor);
+    if (coreMetricManager != null) {
+      coreMetricManager.afterCoreSetName();
+    }
   }
 
   public String getLogId()
@@ -397,6 +413,15 @@ public final class SolrCore implements SolrInfoMBean, Closeable {
   }
 
   /**
+   * Returns the {@link SolrCoreMetricManager} for this core.
+   *
+   * @return the {@link SolrCoreMetricManager} for this core
+   */
+  public SolrCoreMetricManager getCoreMetricManager() {
+    return coreMetricManager;
+  }
+
+  /**
    * Returns a Map of name vs SolrInfoMBean objects. The returned map is an instance of
    * a ConcurrentHashMap and therefore no synchronization is needed for putting, removing
    * or iterating over it.
@@ -838,6 +863,18 @@ public final class SolrCore implements SolrInfoMBean, Closeable {
 
     checkVersionFieldExistsInSchema(schema, coreDescriptor);
 
+    // Initialize the metrics manager
+    this.coreMetricManager = initCoreMetricManager(config);
+
+    SolrMetricManager metricManager = this.coreDescriptor.getCoreContainer().getMetricManager();
+
+    // initialize searcher-related metrics
+    newSearcherCounter = metricManager.counter(coreMetricManager.getRegistryName(), "newSearcher");
+    newSearcherTimer = metricManager.timer(coreMetricManager.getRegistryName(), "newSearcherTime");
+    newSearcherWarmupTimer = metricManager.timer(coreMetricManager.getRegistryName(), "newSearcherWarmup");
+    newSearcherMaxReachedCounter = metricManager.counter(coreMetricManager.getRegistryName(), "newSearcherMaxReached");
+    newSearcherOtherErrorsCounter = metricManager.counter(coreMetricManager.getRegistryName(), "newSearcherErrors");
+
     // Initialize JMX
     this.infoRegistry = initInfoRegistry(name, config);
     infoRegistry.put("fieldCache", new SolrFieldCacheMBean());
@@ -1041,6 +1078,19 @@ public final class SolrCore implements SolrInfoMBean, Closeable {
     setLatestSchema(schema);
   }
 
+  /**
+   * Initializes the core's {@link SolrCoreMetricManager} with a given configuration.
+   * If metric reporters are configured, they are also initialized for this core.
+   *
+   * @param config the given configuration
+   * @return an instance of {@link SolrCoreMetricManager}
+   */
+  private SolrCoreMetricManager initCoreMetricManager(SolrConfig config) {
+    SolrCoreMetricManager coreMetricManager = new SolrCoreMetricManager(this);
+    coreMetricManager.loadReporters();
+    return coreMetricManager;
+  }
+
   private Map<String,SolrInfoMBean> initInfoRegistry(String name, SolrConfig config) {
     if (config.jmxConfig.enabled) {
       return new JmxMonitoredMap<String, SolrInfoMBean>(name, String.valueOf(this.hashCode()), config.jmxConfig);
@@ -1361,6 +1411,15 @@ public final class SolrCore implements SolrInfoMBean, Closeable {
       }
     }
 
+    try {
+      coreMetricManager.close();
+    } catch (Throwable e) {
+      SolrException.log(log, e);
+      if (e instanceof  Error) {
+        throw (Error) e;
+      }
+    }
+
     // Close the snapshots meta-data directory.
     Directory snapshotsDir = snapshotMgr.getSnapshotsDir();
     try {
@@ -1920,12 +1979,14 @@ public final class SolrCore implements SolrInfoMBean, Closeable {
         // first: increment count to signal other threads that we are
         //        opening a new searcher.
         onDeckSearchers++;
+        newSearcherCounter.inc();
         if (onDeckSearchers < 1) {
           // should never happen... just a sanity check
           log.error(logid + "ERROR!!! onDeckSearchers is " + onDeckSearchers);
           onDeckSearchers = 1;  // reset
         } else if (onDeckSearchers > maxWarmingSearchers) {
           onDeckSearchers--;
+          newSearcherMaxReachedCounter.inc();
           try {
             searcherLock.wait();
           } catch (InterruptedException e) {
@@ -1947,6 +2008,7 @@ public final class SolrCore implements SolrInfoMBean, Closeable {
     boolean success = false;
 
     openSearcherLock.lock();
+    Timer.Context timerContext = newSearcherTimer.time();
     try {
       searchHolder = openNewSearcher(updateHandlerReopens, false);
        // the searchHolder will be incremented once already (and it will eventually be assigned to _searcher when registered)
@@ -1989,6 +2051,7 @@ public final class SolrCore implements SolrInfoMBean, Closeable {
         // should this go before the other event handlers or after?
         if (currSearcher != null) {
           future = searcherExecutor.submit(() -> {
+            Timer.Context warmupContext = newSearcherWarmupTimer.time();
             try {
               newSearcher.warm(currSearcher);
             } catch (Throwable e) {
@@ -1996,6 +2059,8 @@ public final class SolrCore implements SolrInfoMBean, Closeable {
               if (e instanceof Error) {
                 throw (Error) e;
               }
+            } finally {
+              warmupContext.close();
             }
             return null;
           });
@@ -2076,7 +2141,10 @@ public final class SolrCore implements SolrInfoMBean, Closeable {
       throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, e);
     } finally {
 
+      timerContext.close();
+
       if (!success) {
+        newSearcherOtherErrorsCounter.inc();;
         synchronized (searcherLock) {
           onDeckSearchers--;
 
@@ -2750,6 +2818,11 @@ public final class SolrCore implements SolrInfoMBean, Closeable {
 
   public void registerInfoBean(String name, SolrInfoMBean solrInfoMBean) {
     infoRegistry.put(name, solrInfoMBean);
+
+    if (solrInfoMBean instanceof SolrMetricProducer) {
+      SolrMetricProducer producer = (SolrMetricProducer) solrInfoMBean;
+      coreMetricManager.registerMetricProducer(name, producer);
+    }
   }
 
   private static boolean checkStale(SolrZkClient zkClient,  String zkPath, int currentVersion)  {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8bbdb624/solr/core/src/java/org/apache/solr/core/SolrInfoMBean.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/core/SolrInfoMBean.java b/solr/core/src/java/org/apache/solr/core/SolrInfoMBean.java
index c119e2f..c5fb84b 100644
--- a/solr/core/src/java/org/apache/solr/core/SolrInfoMBean.java
+++ b/solr/core/src/java/org/apache/solr/core/SolrInfoMBean.java
@@ -29,7 +29,15 @@ import org.apache.solr.common.util.NamedList;
  */
 public interface SolrInfoMBean {
 
-  public enum Category { CORE, QUERYHANDLER, UPDATEHANDLER, CACHE, HIGHLIGHTING, QUERYPARSER, OTHER };
+  /**
+   * Category of {@link SolrCore} component.
+   */
+  enum Category { CORE, QUERYHANDLER, UPDATEHANDLER, CACHE, HIGHLIGHTING, QUERYPARSER, OTHER }
+
+  /**
+   * Top-level group of beans for a subsystem.
+   */
+  enum Group { jvm, jetty, http, node, core }
 
   /**
    * Simple common usage name, e.g. BasicQueryHandler,

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8bbdb624/solr/core/src/java/org/apache/solr/core/SolrXmlConfig.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/core/SolrXmlConfig.java b/solr/core/src/java/org/apache/solr/core/SolrXmlConfig.java
index 65b248d..49d9ae5 100644
--- a/solr/core/src/java/org/apache/solr/core/SolrXmlConfig.java
+++ b/solr/core/src/java/org/apache/solr/core/SolrXmlConfig.java
@@ -95,7 +95,8 @@ public class SolrXmlConfig {
     configBuilder.setSolrProperties(loadProperties(config));
     if (cloudConfig != null)
       configBuilder.setCloudConfig(cloudConfig);
-    configBuilder.setBackupRepositoryPlugins((getBackupRepositoryPluginInfos(config)));
+    configBuilder.setBackupRepositoryPlugins(getBackupRepositoryPluginInfos(config));
+    configBuilder.setMetricReporterPlugins(getMetricReporterPluginInfos(config));
     return fillSolrSection(configBuilder, entries);
   }
 
@@ -436,5 +437,16 @@ public class SolrXmlConfig {
     }
     return configs;
   }
+
+  private static PluginInfo[] getMetricReporterPluginInfos(Config config) {
+    NodeList nodes = (NodeList) config.evaluate("solr/metrics/reporter", XPathConstants.NODESET);
+    if (nodes == null || nodes.getLength() == 0)
+      return new PluginInfo[0];
+    PluginInfo[] configs = new PluginInfo[nodes.getLength()];
+    for (int i = 0; i < nodes.getLength(); i++) {
+      configs[i] = new PluginInfo(nodes.item(i), "SolrMetricReporter", true, true);
+    }
+    return configs;
+  }
 }
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8bbdb624/solr/core/src/java/org/apache/solr/handler/RequestHandlerBase.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/RequestHandlerBase.java b/solr/core/src/java/org/apache/solr/handler/RequestHandlerBase.java
index 218b6de..85597dc 100644
--- a/solr/core/src/java/org/apache/solr/handler/RequestHandlerBase.java
+++ b/solr/core/src/java/org/apache/solr/handler/RequestHandlerBase.java
@@ -18,8 +18,11 @@ package org.apache.solr.handler;
 
 import java.lang.invoke.MethodHandles;
 import java.net.URL;
-import java.util.concurrent.atomic.LongAdder;
+import java.util.Arrays;
+import java.util.Collection;
 
+import com.codahale.metrics.Counter;
+import com.codahale.metrics.Meter;
 import com.codahale.metrics.Timer;
 import org.apache.solr.common.SolrException;
 import org.apache.solr.common.params.SolrParams;
@@ -29,12 +32,14 @@ import org.apache.solr.common.util.SuppressForbidden;
 import org.apache.solr.core.PluginBag;
 import org.apache.solr.core.PluginInfo;
 import org.apache.solr.core.SolrInfoMBean;
+import org.apache.solr.metrics.SolrMetricManager;
+import org.apache.solr.metrics.SolrMetricProducer;
 import org.apache.solr.request.SolrQueryRequest;
 import org.apache.solr.request.SolrRequestHandler;
 import org.apache.solr.response.SolrQueryResponse;
 import org.apache.solr.search.SyntaxError;
 import org.apache.solr.util.SolrPluginUtils;
-import org.apache.solr.util.stats.TimerUtils;
+import org.apache.solr.util.stats.MetricUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -43,7 +48,7 @@ import static org.apache.solr.core.RequestParams.USEPARAM;
 /**
  *
  */
-public abstract class RequestHandlerBase implements SolrRequestHandler, SolrInfoMBean, NestedRequestHandler {
+public abstract class RequestHandlerBase implements SolrRequestHandler, SolrInfoMBean, SolrMetricProducer, NestedRequestHandler {
 
   protected NamedList initArgs = null;
   protected SolrParams defaults;
@@ -52,11 +57,12 @@ public abstract class RequestHandlerBase implements SolrRequestHandler, SolrInfo
   protected boolean httpCaching = true;
 
   // Statistics
-  private final LongAdder numRequests = new LongAdder();
-  private final LongAdder numServerErrors = new LongAdder();
-  private final LongAdder numClientErrors = new LongAdder();
-  private final LongAdder numTimeouts = new LongAdder();
-  private final Timer requestTimes = new Timer();
+  private Meter numErrors = new Meter();
+  private Meter numServerErrors = new Meter();
+  private Meter numClientErrors = new Meter();
+  private Meter numTimeouts = new Meter();
+  private Counter requests = new Counter();
+  private Timer requestTimes = new Timer();
 
   private final long handlerStart;
 
@@ -126,6 +132,17 @@ public abstract class RequestHandlerBase implements SolrRequestHandler, SolrInfo
 
   }
 
+  @Override
+  public Collection<String> initializeMetrics(SolrMetricManager manager, String registryName, String scope) {
+    numErrors = manager.meter(registryName, "errors", getCategory().toString(), scope);
+    numServerErrors = manager.meter(registryName, "serverErrors", getCategory().toString(), scope);
+    numClientErrors = manager.meter(registryName, "clientErrors", getCategory().toString(), scope);
+    numTimeouts = manager.meter(registryName, "timeouts", getCategory().toString(), scope);
+    requests = manager.counter(registryName, "requests", getCategory().toString(), scope);
+    requestTimes = manager.timer(registryName, "requestTimes", getCategory().toString(), scope);
+    return Arrays.asList("errors", "serverErrors", "clientErrors", "timeouts", "requestTimes", "requests");
+  }
+
   public static SolrParams getSolrParamsFromNamedList(NamedList args, String key) {
     Object o = args.get(key);
     if (o != null && o instanceof NamedList) {
@@ -142,7 +159,7 @@ public abstract class RequestHandlerBase implements SolrRequestHandler, SolrInfo
 
   @Override
   public void handleRequest(SolrQueryRequest req, SolrQueryResponse rsp) {
-    numRequests.increment();
+    requests.inc();
     Timer.Context timer = requestTimes.time();
     try {
       if(pluginInfo != null && pluginInfo.attributes.containsKey(USEPARAM)) req.getContext().put(USEPARAM,pluginInfo.attributes.get(USEPARAM));
@@ -156,7 +173,7 @@ public abstract class RequestHandlerBase implements SolrRequestHandler, SolrInfo
         Object partialResults = header.get(SolrQueryResponse.RESPONSE_HEADER_PARTIAL_RESULTS_KEY);
         boolean timedOut = partialResults == null ? false : (Boolean)partialResults;
         if( timedOut ) {
-          numTimeouts.increment();
+          numTimeouts.mark();
           rsp.setHttpCaching(false);
         }
       }
@@ -182,14 +199,14 @@ public abstract class RequestHandlerBase implements SolrRequestHandler, SolrInfo
       if (incrementErrors) {
         SolrException.log(log, e);
 
+        numErrors.mark();
         if (isServerError) {
-          numServerErrors.increment();
+          numServerErrors.mark();
         } else {
-          numClientErrors.increment();
+          numClientErrors.mark();
         }
       }
-    }
-    finally {
+    } finally {
       timer.stop();
     }
   }
@@ -268,15 +285,14 @@ public abstract class RequestHandlerBase implements SolrRequestHandler, SolrInfo
   public NamedList<Object> getStatistics() {
     NamedList<Object> lst = new SimpleOrderedMap<>();
     lst.add("handlerStart",handlerStart);
-    lst.add("requests", numRequests.longValue());
-    lst.add("errors", numServerErrors.longValue() + numClientErrors.longValue());
-    lst.add("serverErrors", numServerErrors.longValue());
-    lst.add("clientErrors", numClientErrors.longValue());
-    lst.add("timeouts", numTimeouts.longValue());
-    TimerUtils.addMetrics(lst, requestTimes);
+    lst.add("requests", requests.getCount());
+    lst.add("errors", numErrors.getCount());
+    lst.add("serverErrors", numServerErrors.getCount());
+    lst.add("clientErrors", numClientErrors.getCount());
+    lst.add("timeouts", numTimeouts.getCount());
+    MetricUtils.addMetrics(lst, requestTimes);
     return lst;
   }
-
 }
 
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8bbdb624/solr/core/src/java/org/apache/solr/handler/admin/MetricsHandler.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/handler/admin/MetricsHandler.java b/solr/core/src/java/org/apache/solr/handler/admin/MetricsHandler.java
new file mode 100644
index 0000000..78b2045
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/handler/admin/MetricsHandler.java
@@ -0,0 +1,164 @@
+/*
+ * 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.solr.handler.admin;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import com.codahale.metrics.Counter;
+import com.codahale.metrics.Gauge;
+import com.codahale.metrics.Histogram;
+import com.codahale.metrics.Meter;
+import com.codahale.metrics.MetricFilter;
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.Timer;
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.util.NamedList;
+import org.apache.solr.common.util.StrUtils;
+import org.apache.solr.core.CoreContainer;
+import org.apache.solr.core.SolrCore;
+import org.apache.solr.handler.RequestHandlerBase;
+import org.apache.solr.metrics.SolrMetricManager;
+import org.apache.solr.request.SolrQueryRequest;
+import org.apache.solr.response.SolrQueryResponse;
+import org.apache.solr.security.AuthorizationContext;
+import org.apache.solr.security.PermissionNameProvider;
+import org.apache.solr.util.stats.MetricUtils;
+
+/**
+ * Request handler to return metrics
+ */
+public class MetricsHandler extends RequestHandlerBase implements PermissionNameProvider {
+  final CoreContainer container;
+  final SolrMetricManager metricManager;
+
+  public MetricsHandler() {
+    this.container = null;
+    this.metricManager = null;
+  }
+
+  public MetricsHandler(CoreContainer container) {
+    this.container = container;
+    this.metricManager = this.container.getMetricManager();
+  }
+
+  @Override
+  public Name getPermissionName(AuthorizationContext request) {
+    return Name.METRICS_READ_PERM;
+  }
+
+  @Override
+  public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
+    if (container == null) {
+      throw new SolrException(SolrException.ErrorCode.INVALID_STATE, "Core container instance not initialized");
+    }
+
+    List<MetricType> metricTypes = parseMetricTypes(req);
+    List<MetricFilter> metricFilters = metricTypes.stream().map(MetricType::asMetricFilter).collect(Collectors.toList());
+    List<Group> requestedGroups = parseGroups(req);
+
+    NamedList response = new NamedList();
+    for (Group group : requestedGroups) {
+      String registryName = SolrMetricManager.getRegistryName(group);
+      if (group == Group.core) {
+        // this requires special handling because of the way we create registry name for a core (deeply nested)
+        container.getAllCoreNames().forEach(s -> {
+          String coreRegistryName;
+          try (SolrCore core = container.getCore(s)) {
+            coreRegistryName = core.getCoreMetricManager().getRegistryName();
+          }
+          MetricRegistry registry = metricManager.registry(coreRegistryName);
+          response.add(coreRegistryName, MetricUtils.toNamedList(registry, metricFilters));
+        });
+      } else {
+        MetricRegistry registry = metricManager.registry(registryName);
+        response.add(registryName, MetricUtils.toNamedList(registry, metricFilters));
+      }
+    }
+    rsp.getValues().add("metrics", response);
+  }
+
+  private List<Group> parseGroups(SolrQueryRequest req) {
+    String[] groupStr = req.getParams().getParams("group");
+    List<String> groups = Collections.emptyList();
+    if (groupStr != null && groupStr.length > 0) {
+      groups = new ArrayList<>();
+      for (String g : groupStr) {
+        groups.addAll(StrUtils.splitSmart(g, ','));
+      }
+    }
+
+    List<Group> requestedGroups = Arrays.asList(Group.values()); // by default we return all groups
+    try {
+      if (groups.size() > 0 && !groups.contains("all")) {
+        requestedGroups = groups.stream().map(String::trim).map(Group::valueOf).collect(Collectors.toList());
+      }
+    } catch (IllegalArgumentException e) {
+      throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Invalid group in: " + groups + " specified. Must be one of (all, jvm, jetty, http, node, core)", e);
+    }
+    return requestedGroups;
+  }
+
+  private List<MetricType> parseMetricTypes(SolrQueryRequest req) {
+    String[] typeStr = req.getParams().getParams("type");
+    List<String> types = Collections.emptyList();
+    if (typeStr != null && typeStr.length > 0)  {
+      types = new ArrayList<>();
+      for (String type : typeStr) {
+        types.addAll(StrUtils.splitSmart(type, ','));
+      }
+    }
+
+    List<MetricType> metricTypes = Collections.singletonList(MetricType.all); // include all metrics by default
+    try {
+      if (types.size() > 0) {
+        metricTypes = types.stream().map(String::trim).map(MetricType::valueOf).collect(Collectors.toList());
+      }
+    } catch (IllegalArgumentException e) {
+      throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Invalid metric type in: " + types + " specified. Must be one of (all, meter, timer, histogram, counter, gauge)", e);
+    }
+    return metricTypes;
+  }
+
+  @Override
+  public String getDescription() {
+    return "A handler to return all the metrics gathered by Solr";
+  }
+
+  enum MetricType {
+    histogram(Histogram.class),
+    meter(Meter.class),
+    timer(Timer.class),
+    counter(Counter.class),
+    gauge(Gauge.class),
+    all(null);
+
+    private final Class klass;
+
+    MetricType(Class klass) {
+      this.klass = klass;
+    }
+
+    public MetricFilter asMetricFilter() {
+      return (name, metric) -> klass == null || klass.isInstance(metric);
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8bbdb624/solr/core/src/java/org/apache/solr/metrics/SolrCoreMetricManager.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/metrics/SolrCoreMetricManager.java b/solr/core/src/java/org/apache/solr/metrics/SolrCoreMetricManager.java
new file mode 100644
index 0000000..0e5403b
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/metrics/SolrCoreMetricManager.java
@@ -0,0 +1,154 @@
+/*
+ * 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.solr.metrics;
+
+import java.io.Closeable;
+import java.io.IOException;
+import java.util.Collection;
+import java.lang.invoke.MethodHandles;
+
+import org.apache.solr.core.NodeConfig;
+import org.apache.solr.core.PluginInfo;
+import org.apache.solr.core.SolrCore;
+import org.apache.solr.core.SolrInfoMBean;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Responsible for collecting metrics from {@link SolrMetricProducer}'s
+ * and exposing metrics to {@link SolrMetricReporter}'s.
+ */
+public class SolrCoreMetricManager implements Closeable {
+
+  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+  private final SolrCore core;
+  private final SolrMetricManager metricManager;
+  private String registryName;
+
+  /**
+   * Constructs a metric manager.
+   *
+   * @param core the metric manager's core
+   */
+  public SolrCoreMetricManager(SolrCore core) {
+    this.core = core;
+    this.metricManager = core.getCoreDescriptor().getCoreContainer().getMetricManager();
+    registryName = createRegistryName(core.getCoreDescriptor().getCollectionName(), core.getName());
+  }
+
+  /**
+   * Load reporters configured globally and specific to {@link org.apache.solr.core.SolrInfoMBean.Group#core}
+   * group or with a registry name specific to this core.
+   */
+  public void loadReporters() {
+    NodeConfig nodeConfig = core.getCoreDescriptor().getCoreContainer().getConfig();
+    PluginInfo[] pluginInfos = nodeConfig.getMetricReporterPlugins();
+    metricManager.loadReporters(pluginInfos, core.getResourceLoader(), SolrInfoMBean.Group.core, registryName);
+  }
+
+  /**
+   * Make sure that metrics already collected that correspond to the old core name
+   * are carried over and will be used under the new core name.
+   * This method also reloads reporters so that they use the new core name.
+   */
+  public void afterCoreSetName() {
+    String oldRegistryName = registryName;
+    registryName = createRegistryName(core.getCoreDescriptor().getCollectionName(), core.getName());
+    if (oldRegistryName.equals(registryName)) {
+      return;
+    }
+    // close old reporters
+    metricManager.closeReporters(oldRegistryName);
+    metricManager.moveMetrics(oldRegistryName, registryName, null);
+    // old registry is no longer used - we have moved the metrics
+    metricManager.removeRegistry(oldRegistryName);
+    // load reporters again, using the new core name
+    loadReporters();
+  }
+
+  /**
+   * Registers a mapping of name/metric's with the manager's metric registry.
+   *
+   * @param scope     the scope of the metrics to be registered (e.g. `/admin/ping`)
+   * @param producer  producer of metrics to be registered
+   */
+  public void registerMetricProducer(String scope, SolrMetricProducer producer) {
+    if (scope == null || producer == null || producer.getCategory() == null) {
+      throw new IllegalArgumentException("registerMetricProducer() called with illegal arguments: " +
+          "scope = " + scope + ", producer = " + producer);
+    }
+    Collection<String> registered = producer.initializeMetrics(metricManager, getRegistryName(), scope);
+    if (registered == null || registered.isEmpty()) {
+      throw new IllegalArgumentException("registerMetricProducer() did not register any metrics " +
+      "for scope = " + scope + ", producer = " + producer);
+    }
+  }
+
+  /**
+   * Closes reporters specific to this core.
+   */
+  @Override
+  public void close() throws IOException {
+    metricManager.closeReporters(getRegistryName());
+  }
+
+  public SolrCore getCore() {
+    return core;
+  }
+
+  /**
+   * Retrieves the metric registry name of the manager.
+   *
+   * In order to make it easier for reporting tools to aggregate metrics from
+   * different cores that logically belong to a single collection we convert the
+   * core name into a dot-separated hierarchy of: collection name, shard name (with optional split)
+   * and replica name.
+   *
+   * <p>For example, when the core name looks like this but it's NOT a SolrCloud collection:
+   * <code>my_collection_shard1_1_replica1</code> then this will be used as the registry name (plus
+   * the required <code>solr.core</code> prefix). However,
+   * if this is a SolrCloud collection <code>my_collection</code> then the registry name will become
+   * <code>solr.core.my_collection.shard1_1.replica1</code>.</p>
+   *
+   *
+   * @return the metric registry name of the manager.
+   */
+  public String getRegistryName() {
+    return registryName;
+  }
+
+  /* package visibility for tests. */
+  String createRegistryName(String collectionName, String coreName) {
+    if (collectionName == null || (collectionName != null && !coreName.startsWith(collectionName + "_"))) {
+      // single core, or unknown naming scheme
+      return SolrMetricManager.getRegistryName(SolrInfoMBean.Group.core, coreName);
+    }
+    // split "collection1_shard1_1_replica1" into parts
+    String str = coreName.substring(collectionName.length() + 1);
+    String shard;
+    String replica = null;
+    int pos = str.lastIndexOf("_replica");
+    if (pos == -1) { // ?? no _replicaN part ??
+      shard = str;
+    } else {
+      shard = str.substring(0, pos);
+      replica = str.substring(pos + 1);
+    }
+    return SolrMetricManager.getRegistryName(SolrInfoMBean.Group.core, collectionName, shard, replica);
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8bbdb624/solr/core/src/java/org/apache/solr/metrics/SolrMetricInfo.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/metrics/SolrMetricInfo.java b/solr/core/src/java/org/apache/solr/metrics/SolrMetricInfo.java
new file mode 100644
index 0000000..f0bc8a1
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/metrics/SolrMetricInfo.java
@@ -0,0 +1,104 @@
+/*
+ * 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.solr.metrics;
+
+import com.codahale.metrics.MetricRegistry;
+import org.apache.solr.core.SolrInfoMBean;
+
+/**
+ * Wraps meta-data for a metric.
+ */
+public final class SolrMetricInfo {
+  public final String name;
+  public final String scope;
+  public final SolrInfoMBean.Category category;
+
+  /**
+   * Creates a new instance of {@link SolrMetricInfo}.
+   *
+   * @param category the category of the metric (e.g. `QUERYHANDLERS`)
+   * @param scope    the scope of the metric (e.g. `/admin/ping`)
+   * @param name     the name of the metric (e.g. `Requests`)
+   */
+  public SolrMetricInfo(SolrInfoMBean.Category category, String scope, String name) {
+    this.name = name;
+    this.scope = scope;
+    this.category = category;
+  }
+
+  public static SolrMetricInfo of(String fullName) {
+    if (fullName == null || fullName.isEmpty()) {
+      return null;
+    }
+    String[] names = fullName.split("\\.");
+    if (names.length < 3) { // not a valid info
+      return null;
+    }
+    // check top-level name for valid category
+    SolrInfoMBean.Category category;
+    try {
+      category = SolrInfoMBean.Category.valueOf(names[0]);
+    } catch (IllegalArgumentException e) { // not a valid category
+      return null;
+    }
+    String scope = names[1];
+    String name = fullName.substring(names[0].length() + names[1].length() + 2);
+    return new SolrMetricInfo(category, scope, name);
+  }
+
+  /**
+   * Returns the metric name defined by this object.
+   * For example, if the name is `Requests`, scope is `/admin/ping`,
+   * and category is `QUERYHANDLERS`, then the metric name is
+   * `QUERYHANDLERS./admin/ping.Requests`.
+   *
+   * @return the metric name defined by this object
+   */
+  public String getMetricName() {
+    return MetricRegistry.name(category.toString(), scope, name);
+  }
+
+  @Override
+  public String toString() {
+    return "SolrMetricInfo{" +
+        "name='" + name + '\'' +
+        ", scope='" + scope + '\'' +
+        ", category=" + category +
+        '}';
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+
+    SolrMetricInfo that = (SolrMetricInfo) o;
+
+    if (name != null ? !name.equals(that.name) : that.name != null) return false;
+    if (scope != null ? !scope.equals(that.scope) : that.scope != null) return false;
+    return category == that.category;
+
+  }
+
+  @Override
+  public int hashCode() {
+    int result = name != null ? name.hashCode() : 0;
+    result = 31 * result + (scope != null ? scope.hashCode() : 0);
+    result = 31 * result + (category != null ? category.hashCode() : 0);
+    return result;
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8bbdb624/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java b/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java
new file mode 100644
index 0000000..3f51aef
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/metrics/SolrMetricManager.java
@@ -0,0 +1,652 @@
+/*
+ * 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.solr.metrics;
+
+import java.io.IOException;
+import java.lang.invoke.MethodHandles;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+import com.codahale.metrics.Counter;
+import com.codahale.metrics.Histogram;
+import com.codahale.metrics.Meter;
+import com.codahale.metrics.Metric;
+import com.codahale.metrics.MetricFilter;
+import com.codahale.metrics.MetricRegistry;
+import com.codahale.metrics.MetricSet;
+import com.codahale.metrics.SharedMetricRegistries;
+import com.codahale.metrics.Timer;
+import org.apache.solr.core.PluginInfo;
+import org.apache.solr.core.SolrInfoMBean;
+import org.apache.solr.core.SolrResourceLoader;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This class maintains a repository of named {@link MetricRegistry} instances, and provides several
+ * helper methods for managing various aspects of metrics reporting:
+ * <ul>
+ *   <li>registry creation, clearing and removal,</li>
+ *   <li>creation of most common metric implementations,</li>
+ *   <li>management of {@link SolrMetricReporter}-s specific to a named registry.</li>
+ * </ul>
+ * {@link MetricRegistry} instances are automatically created when first referenced by name. Similarly,
+ * instances of {@link Metric} implementations, such as {@link Meter}, {@link Counter}, {@link Timer} and
+ * {@link Histogram} are automatically created and registered under hierarchical names, in a specified
+ * registry, when {@link #meter(String, String, String...)} and other similar methods are called.
+ * <p>This class enforces a common prefix ({@link #REGISTRY_NAME_PREFIX}) in all registry
+ * names.</p>
+ * <p>Solr uses several different registries for collecting metrics belonging to different groups, using
+ * {@link org.apache.solr.core.SolrInfoMBean.Group} as the main name of the registry (plus the
+ * above-mentioned prefix). Instances of {@link SolrMetricManager} are created for each {@link org.apache.solr.core.CoreContainer},
+ * and most registries are local to each instance, with the exception of two global registries:
+ * <code>solr.jetty</code> and <code>solr.jvm</code>, which are shared between all {@link org.apache.solr.core.CoreContainer}-s</p>
+ */
+public class SolrMetricManager {
+
+  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
+
+  public static final String REGISTRY_NAME_PREFIX = "solr.";
+
+  public static final String JETTY_REGISTRY = REGISTRY_NAME_PREFIX + SolrInfoMBean.Group.jetty.toString();
+
+  public static final String JVM_REGISTRY = REGISTRY_NAME_PREFIX + SolrInfoMBean.Group.jvm.toString();
+
+  private final ConcurrentMap<String, MetricRegistry> registries = new ConcurrentHashMap<>();
+
+  // these reporters are per CoreContainer
+  private final Map<String, Map<String, SolrMetricReporter>> reporters = new HashMap<>();
+
+  // these reporters are per JVM
+  private static final Map<String, Map<String, SolrMetricReporter>> sharedReporters = new HashMap<>();
+
+  private final Lock reportersLock = new ReentrantLock();
+
+  public SolrMetricManager() { }
+
+  /**
+   * An implementation of {@link MetricFilter} that selects metrics
+   * with names that start with a prefix.
+   */
+  public static class PrefixFilter implements MetricFilter {
+    private final String prefix;
+    private final Set<String> matched = new HashSet<>();
+
+    /**
+     * Create a filter that uses the provided prefix.
+     * @param prefix prefix to use, must not be null. If empty then any
+     *               name will match.
+     */
+    public PrefixFilter(String prefix) {
+      Objects.requireNonNull(prefix);
+      this.prefix = prefix;
+    }
+
+    @Override
+    public boolean matches(String name, Metric metric) {
+      if (prefix.isEmpty()) {
+        matched.add(name);
+        return true;
+      }
+      if (name.startsWith(prefix)) {
+        matched.add(name);
+        return true;
+      } else {
+        return false;
+      }
+    }
+
+    /**
+     * Return the set of names that matched this filter.
+     * @return matching names
+     */
+    public Set<String> getMatched() {
+      return Collections.unmodifiableSet(matched);
+    }
+
+    /**
+     * Clear the set of names that matched.
+     */
+    public void reset() {
+      matched.clear();
+    }
+  }
+
+  /**
+   * Return a set of existing registry names.
+   */
+  public Set<String> registryNames() {
+    Set<String> set = new HashSet<>();
+    set.addAll(registries.keySet());
+    set.addAll(SharedMetricRegistries.names());
+    return Collections.unmodifiableSet(set);
+  }
+
+  /**
+   * Get (or create if not present) a named registry
+   * @param registry name of the registry
+   * @return existing or newly created registry
+   */
+  public MetricRegistry registry(String registry) {
+    registry = overridableRegistryName(registry);
+    if (JETTY_REGISTRY.equals(registry) || JVM_REGISTRY.equals(registry)) {
+      return SharedMetricRegistries.getOrCreate(registry);
+    } else {
+      final MetricRegistry existing = registries.get(registry);
+      if (existing == null) {
+        final MetricRegistry created = new MetricRegistry();
+        final MetricRegistry raced = registries.putIfAbsent(registry, created);
+        if (raced == null) {
+          return created;
+        } else {
+          return raced;
+        }
+      } else {
+        return existing;
+      }
+    }
+  }
+
+  /**
+   * Remove a named registry.
+   * @param registry name of the registry to remove
+   */
+  public void removeRegistry(String registry) {
+    // close any reporters for this registry first
+    closeReporters(registry);
+    // make sure we use a name with prefix, with overrides
+    registry = overridableRegistryName(registry);
+    if (JETTY_REGISTRY.equals(registry) || JVM_REGISTRY.equals(registry)) {
+      SharedMetricRegistries.remove(registry);
+    } else {
+      registries.remove(registry);
+    }
+  }
+
+  /**
+   * Move all matching metrics from one registry to another. This is useful eg. during
+   * {@link org.apache.solr.core.SolrCore} rename or swap operations.
+   * @param fromRegistry source registry
+   * @param toRegistry target registry
+   * @param filter optional {@link MetricFilter} to select what metrics to move. If null
+   *               then all metrics will be moved.
+   */
+  public void moveMetrics(String fromRegistry, String toRegistry, MetricFilter filter) {
+    MetricRegistry from = registry(fromRegistry);
+    MetricRegistry to = registry(toRegistry);
+    if (from == to) {
+      return;
+    }
+    if (filter == null) {
+      to.registerAll(from);
+      from.removeMatching(MetricFilter.ALL);
+    } else {
+      for (Map.Entry<String, Metric> entry : from.getMetrics().entrySet()) {
+        if (filter.matches(entry.getKey(), entry.getValue())) {
+          to.register(entry.getKey(), entry.getValue());
+        }
+      }
+      from.removeMatching(filter);
+    }
+  }
+
+  /**
+   * Register all metrics in the provided {@link MetricSet}, optionally skipping those that
+   * already exist.
+   * @param registry registry name
+   * @param metrics metric set to register
+   * @param force if true then already existing metrics with the same name will be replaced.
+   *                     When false and a metric with the same name already exists an exception
+   *                     will be thrown.
+   * @param metricPath (optional) additional top-most metric name path elements
+   * @throws Exception if a metric with this name already exists.
+   */
+  public void registerAll(String registry, MetricSet metrics, boolean force, String... metricPath) throws Exception {
+    MetricRegistry metricRegistry = registry(registry);
+    synchronized (metricRegistry) {
+      Map<String, Metric> existingMetrics = metricRegistry.getMetrics();
+      for (Map.Entry<String, Metric> entry : metrics.getMetrics().entrySet()) {
+        String fullName = mkName(entry.getKey(), metricPath);
+        if (force && existingMetrics.containsKey(fullName)) {
+          metricRegistry.remove(fullName);
+        }
+        metricRegistry.register(fullName, entry.getValue());
+      }
+    }
+  }
+
+  /**
+   * Remove all metrics from a specified registry.
+   * @param registry registry name
+   */
+  public void clearRegistry(String registry) {
+    registry(registry).removeMatching(MetricFilter.ALL);
+  }
+
+  /**
+   * Remove some metrics from a named registry
+   * @param registry registry name
+   * @param metricPath (optional) top-most metric name path elements. If empty then
+   *        this is equivalent to calling {@link #clearRegistry(String)},
+   *        otherwise non-empty elements will be joined using dotted notation
+   *        to form a fully-qualified prefix. Metrics with names that start
+   *        with the prefix will be removed.
+   * @return set of metrics names that have been removed.
+   */
+  public Set<String> clearMetrics(String registry, String... metricPath) {
+    PrefixFilter filter;
+    if (metricPath == null || metricPath.length == 0) {
+      filter = new PrefixFilter("");
+    } else {
+      String prefix = MetricRegistry.name("", metricPath);
+      filter = new PrefixFilter(prefix);
+    }
+    registry(registry).removeMatching(filter);
+    return filter.getMatched();
+  }
+
+  /**
+   * Create or get an existing named {@link Meter}
+   * @param registry registry name
+   * @param metricName metric name, either final name or a fully-qualified name
+   *                   using dotted notation
+   * @param metricPath (optional) additional top-most metric name path elements
+   * @return existing or a newly created {@link Meter}
+   */
+  public Meter meter(String registry, String metricName, String... metricPath) {
+    return registry(registry).meter(mkName(metricName, metricPath));
+  }
+
+  /**
+   * Create or get an existing named {@link Timer}
+   * @param registry registry name
+   * @param metricName metric name, either final name or a fully-qualified name
+   *                   using dotted notation
+   * @param metricPath (optional) additional top-most metric name path elements
+   * @return existing or a newly created {@link Timer}
+   */
+  public Timer timer(String registry, String metricName, String... metricPath) {
+    return registry(registry).timer(mkName(metricName, metricPath));
+  }
+
+  /**
+   * Create or get an existing named {@link Counter}
+   * @param registry registry name
+   * @param metricName metric name, either final name or a fully-qualified name
+   *                   using dotted notation
+   * @param metricPath (optional) additional top-most metric name path elements
+   * @return existing or a newly created {@link Counter}
+   */
+  public Counter counter(String registry, String metricName, String... metricPath) {
+    return registry(registry).counter(mkName(metricName, metricPath));
+  }
+
+  /**
+   * Create or get an existing named {@link Histogram}
+   * @param registry registry name
+   * @param metricName metric name, either final name or a fully-qualified name
+   *                   using dotted notation
+   * @param metricPath (optional) additional top-most metric name path elements
+   * @return existing or a newly created {@link Histogram}
+   */
+  public Histogram histogram(String registry, String metricName, String... metricPath) {
+    return registry(registry).histogram(mkName(metricName, metricPath));
+  }
+
+  /**
+   * Register an instance of {@link Metric}.
+   * @param registry registry name
+   * @param metric metric instance
+   * @param force if true then an already existing metric with the same name will be replaced.
+   *                     When false and a metric with the same name already exists an exception
+   *                     will be thrown.
+   * @param metricName metric name, either final name or a fully-qualified name
+   *                   using dotted notation
+   * @param metricPath (optional) additional top-most metric name path elements
+   */
+  public void register(String registry, Metric metric, boolean force, String metricName, String... metricPath) {
+    MetricRegistry metricRegistry = registry(registry);
+    String fullName = mkName(metricName, metricPath);
+    synchronized (metricRegistry) {
+      if (force && metricRegistry.getMetrics().containsKey(fullName)) {
+        metricRegistry.remove(fullName);
+      }
+      metricRegistry.register(fullName, metric);
+    }
+  }
+
+
+
+  /**
+   * This method creates a hierarchical name with arbitrary levels of hierarchy
+   * @param name the final segment of the name, must not be null or empty.
+   * @param path optional path segments, starting from the top level. Empty or null
+   *             segments will be skipped.
+   * @return fully-qualified name using dotted notation, with all valid hierarchy
+   * segments prepended to the name.
+   */
+  public static String mkName(String name, String... path) {
+    if (name == null || name.isEmpty()) {
+      throw new IllegalArgumentException("name must not be empty");
+    }
+    if (path == null || path.length == 0) {
+      return name;
+    } else {
+      StringBuilder sb = new StringBuilder();
+      for (String s : path) {
+        if (s == null || s.isEmpty()) {
+          continue;
+        }
+        if (sb.length() > 0) {
+          sb.append('.');
+        }
+        sb.append(s);
+      }
+      if (sb.length() > 0) {
+        sb.append('.');
+      }
+      sb.append(name);
+      return sb.toString();
+    }
+  }
+
+  /**
+   * Allows named registries to be renamed using System properties.
+   * This would be mostly be useful if you want to combine the metrics from a few registries for a single
+   * reporter.
+   * <p>For example, in order to collect metrics from related cores in a single registry you could specify
+   * the following system properties:</p>
+   * <pre>
+   *   ... -Dsolr.core.collection1=solr.core.allCollections -Dsolr.core.collection2=solr.core.allCollections
+   * </pre>
+   * <b>NOTE:</b> Once a registry is renamed in a way that its metrics are combined with another repository
+   * it is no longer possible to retrieve the original metrics until this renaming is removed and the Solr
+   * {@link org.apache.solr.core.SolrInfoMBean.Group} of components that reported to that name is restarted.
+   * @param registry The name of the registry
+   * @return A potentially overridden (via System properties) registry name
+   */
+  public static String overridableRegistryName(String registry) {
+    String fqRegistry = enforcePrefix(registry);
+    return enforcePrefix(System.getProperty(fqRegistry,fqRegistry));
+  }
+
+  /**
+   * Enforces the leading {@link #REGISTRY_NAME_PREFIX} in a name.
+   * @param name input name, possibly without the prefix
+   * @return original name if it contained the prefix, or the
+   * input name with the prefix prepended.
+   */
+  public static String enforcePrefix(String name) {
+    if (name.startsWith(REGISTRY_NAME_PREFIX)) {
+      return name;
+    } else {
+      return new StringBuilder(REGISTRY_NAME_PREFIX).append(name).toString();
+    }
+  }
+
+  /**
+   * Helper method to construct a properly prefixed registry name based on the group.
+   * @param group reporting group
+   * @param names optional child elements of the registry name. If exactly one element is provided
+   *              and it already contains the required prefix and group name then this value will be used,
+   *              and the group parameter will be ignored.
+   * @return fully-qualified and prefixed registry name, with overrides applied.
+   */
+  public static String getRegistryName(SolrInfoMBean.Group group, String... names) {
+    String fullName;
+    String prefix = REGISTRY_NAME_PREFIX + group.toString() + ".";
+    // check for existing prefix and group
+    if (names != null && names.length > 0 && names[0] != null && names[0].startsWith(prefix)) {
+      // assume the first segment already was expanded
+      if (names.length > 1) {
+        String[] newNames = new String[names.length - 1];
+        System.arraycopy(names, 1, newNames, 0, newNames.length);
+        fullName = MetricRegistry.name(names[0], newNames);
+      } else {
+        fullName = MetricRegistry.name(names[0]);
+      }
+    } else {
+      fullName = MetricRegistry.name(group.toString(), names);
+    }
+    return overridableRegistryName(fullName);
+  }
+
+  // reporter management
+
+  /**
+   * Create and register {@link SolrMetricReporter}-s specific to a {@link org.apache.solr.core.SolrInfoMBean.Group}.
+   * Note: reporters that specify neither "group" nor "registry" attributes are treated as universal -
+   * they will always be loaded for any group. These two attributes may also contain multiple comma- or
+   * whitespace-separated values, in which case the reporter will be loaded for any matching value from
+   * the list. If both attributes are present then only "group" attribute will be processed.
+   * @param pluginInfos plugin configurations
+   * @param loader resource loader
+   * @param group selected group, not null
+   * @param registryNames optional child registry name elements
+   */
+  public void loadReporters(PluginInfo[] pluginInfos, SolrResourceLoader loader, SolrInfoMBean.Group group, String... registryNames) {
+    if (pluginInfos == null || pluginInfos.length == 0) {
+      return;
+    }
+    String registryName = getRegistryName(group, registryNames);
+    for (PluginInfo info : pluginInfos) {
+      String target = info.attributes.get("group");
+      if (target == null) { // no "group"
+        target = info.attributes.get("registry");
+        if (target != null) {
+          String[] targets = target.split("[\\s,]+");
+          boolean found = false;
+          for (String t : targets) {
+            t = overridableRegistryName(t);
+            if (registryName.equals(t)) {
+              found = true;
+              break;
+            }
+          }
+          if (!found) {
+            continue;
+          }
+        } else {
+          // neither group nor registry specified.
+          // always register this plugin for all groups and registries
+        }
+      } else { // check groups
+        String[] targets = target.split("[\\s,]+");
+        boolean found = false;
+        for (String t : targets) {
+          if (group.toString().equals(t)) {
+            found = true;
+            break;
+          }
+        }
+        if (!found) {
+          continue;
+        }
+      }
+      try {
+        loadReporter(registryName, loader, info);
+      } catch (Exception e) {
+        log.warn("Error loading metrics reporter, plugin info: " + info, e);
+      }
+    }
+  }
+
+  /**
+   * Create and register an instance of {@link SolrMetricReporter}.
+   * @param registry reporter is associated with this registry
+   * @param loader loader to use when creating an instance of the reporter
+   * @param pluginInfo plugin configuration. Plugin "name" and "class" attributes are required.
+   * @throws Exception if any argument is missing or invalid
+   */
+  public void loadReporter(String registry, SolrResourceLoader loader, PluginInfo pluginInfo) throws Exception {
+    if (registry == null || pluginInfo == null || pluginInfo.name == null || pluginInfo.className == null) {
+      throw new IllegalArgumentException("loadReporter called with missing arguments: " +
+          "registry=" + registry + ", loader=" + loader + ", pluginInfo=" + pluginInfo);
+    }
+    // make sure we use a name with prefix, with overrides
+    registry = overridableRegistryName(registry);
+    SolrMetricReporter reporter = loader.newInstance(
+        pluginInfo.className,
+        SolrMetricReporter.class,
+        new String[0],
+        new Class[] { SolrMetricManager.class, String.class },
+        new Object[] { this, registry }
+    );
+    try {
+      reporter.init(pluginInfo);
+    } catch (IllegalStateException e) {
+      throw new IllegalArgumentException("reporter init failed: " + pluginInfo, e);
+    }
+    try {
+      if (!reportersLock.tryLock(10, TimeUnit.SECONDS)) {
+        throw new Exception("Could not obtain lock to modify reporters registry: " + registry);
+      }
+    } catch (InterruptedException e) {
+      throw new Exception("Interrupted while trying to obtain lock to modify reporters registry: " + registry);
+    }
+    try {
+      Map<String, SolrMetricReporter> perRegistry = reporters.get(registry);
+      if (perRegistry == null) {
+        perRegistry = new HashMap<>();
+        reporters.put(registry, perRegistry);
+      }
+      SolrMetricReporter oldReporter = perRegistry.get(pluginInfo.name);
+      if (oldReporter != null) { // close it
+        log.info("Replacing existing reporter '" + pluginInfo.name + "' in registry '" + registry + "': " + oldReporter.toString());
+        oldReporter.close();
+      }
+      perRegistry.put(pluginInfo.name, reporter);
+
+    } finally {
+      reportersLock.unlock();
+    }
+  }
+
+  /**
+   * Close and unregister a named {@link SolrMetricReporter} for a registry.
+   * @param registry registry name
+   * @param name reporter name
+   * @return true if a named reporter existed and was closed.
+   */
+  public boolean closeReporter(String registry, String name) {
+    // make sure we use a name with prefix, with overrides
+    registry = overridableRegistryName(registry);
+    try {
+      if (!reportersLock.tryLock(10, TimeUnit.SECONDS)) {
+        log.warn("Could not obtain lock to modify reporters registry: " + registry);
+        return false;
+      }
+    } catch (InterruptedException e) {
+      log.warn("Interrupted while trying to obtain lock to modify reporters registry: " + registry);
+      return false;
+    }
+    try {
+      Map<String, SolrMetricReporter> perRegistry = reporters.get(registry);
+      if (perRegistry == null) {
+        return false;
+      }
+      SolrMetricReporter reporter = perRegistry.remove(name);
+      if (reporter == null) {
+        return false;
+      }
+      try {
+        reporter.close();
+      } catch (Exception e) {
+        log.warn("Error closing metric reporter, registry=" + registry + ", name=" + name, e);
+      }
+      return true;
+    } finally {
+      reportersLock.unlock();
+    }
+  }
+
+  /**
+   * Close and unregister all {@link SolrMetricReporter}-s for a registry.
+   * @param registry registry name
+   * @return names of closed reporters
+   */
+  public Set<String> closeReporters(String registry) {
+    // make sure we use a name with prefix, with overrides
+    registry = overridableRegistryName(registry);
+    try {
+      if (!reportersLock.tryLock(10, TimeUnit.SECONDS)) {
+        log.warn("Could not obtain lock to modify reporters registry: " + registry);
+        return Collections.emptySet();
+      }
+    } catch (InterruptedException e) {
+      log.warn("Interrupted while trying to obtain lock to modify reporters registry: " + registry);
+      return Collections.emptySet();
+    }
+    log.info("Closing metric reporters for: " + registry);
+    try {
+      Map<String, SolrMetricReporter> perRegistry = reporters.remove(registry);
+      if (perRegistry != null) {
+        for (SolrMetricReporter reporter : perRegistry.values()) {
+          try {
+            reporter.close();
+          } catch (IOException ioe) {
+            log.warn("Exception closing reporter " + reporter, ioe);
+          }
+        }
+        return perRegistry.keySet();
+      } else {
+        return Collections.emptySet();
+      }
+    } finally {
+      reportersLock.unlock();
+    }
+  }
+
+  /**
+   * Get a map of reporters for a registry. Keys are reporter names, values are reporter instances.
+   * @param registry registry name
+   * @return map of reporters and their names, may be empty but never null
+   */
+  public Map<String, SolrMetricReporter> getReporters(String registry) {
+    // make sure we use a name with prefix, with overrides
+    registry = overridableRegistryName(registry);
+    try {
+      if (!reportersLock.tryLock(10, TimeUnit.SECONDS)) {
+        log.warn("Could not obtain lock to modify reporters registry: " + registry);
+        return Collections.emptyMap();
+      }
+    } catch (InterruptedException e) {
+      log.warn("Interrupted while trying to obtain lock to modify reporters registry: " + registry);
+      return Collections.emptyMap();
+    }
+    try {
+      Map<String, SolrMetricReporter> perRegistry = reporters.get(registry);
+      if (perRegistry == null) {
+        return Collections.emptyMap();
+      } else {
+        // defensive copy - the original map may change after we release the lock
+        return Collections.unmodifiableMap(new HashMap<>(perRegistry));
+      }
+    } finally {
+      reportersLock.unlock();
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/8bbdb624/solr/core/src/java/org/apache/solr/metrics/SolrMetricProducer.java
----------------------------------------------------------------------
diff --git a/solr/core/src/java/org/apache/solr/metrics/SolrMetricProducer.java b/solr/core/src/java/org/apache/solr/metrics/SolrMetricProducer.java
new file mode 100644
index 0000000..472931f
--- /dev/null
+++ b/solr/core/src/java/org/apache/solr/metrics/SolrMetricProducer.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.solr.metrics;
+
+import java.util.Collection;
+
+import org.apache.solr.core.SolrInfoMBean;
+
+/**
+ * Extension of {@link SolrInfoMBean} for use by objects that
+ * expose metrics through {@link SolrCoreMetricManager}.
+ */
+public interface SolrMetricProducer extends SolrInfoMBean {
+
+  /**
+   * Initializes metrics specific to this producer
+   * @param manager an instance of {@link SolrMetricManager}
+   * @param registry registry name where metrics are registered
+   * @param scope scope of the metrics (eg. handler name) to separate metrics of
+   *              instances of the same component executing in different contexts
+   * @return registered (or existing) unqualified names of metrics specific to this producer.
+   */
+  Collection<String> initializeMetrics(SolrMetricManager manager, String registry, String scope);
+}